1. 程式人生 > >SpringCloud Zuul過濾器返回值攔截

SpringCloud Zuul過濾器返回值攔截

Zuul作為閘道器服務,是其他各服務對外中轉站,通過Zuul進行請求轉發。這就涉及到部分資料是不能原封返回的,比如服務之間通訊的憑證,使用者的加密資訊等等。

本文中的程式碼已提交至: https://gitee.com/cmlbeliever/springcloud 歡迎Star
實現類在:api-getway工程下的com.cml.springcloud.api.filter.AuthResponseFilter

舉個例子,使用者服務提供一個登入介面,使用者名稱密碼正確後返回一個Token,此Token作為使用者服務的通行證,那麼使用者登入成功後返回的Token就需要進行加密或者防止篡改處理。在到達使用者服務其他介面前,就需要對Token進行校驗,非法的Token就不需要轉發到使用者服務中了,直接在閘道器層返回資訊即可。

要修改服務返回的資訊,需要使用的是Zuul的過濾器。使用時只需要繼承ZuulFilter,實現必要的方法即可。

Zuul提供預設的四種過濾器型別,通過filterType方法進行標識

  • pre:可以在請求被路由之前呼叫
  • route:在路由請求時候被呼叫
  • post:在route和error過濾器之後被呼叫
  • error:處理請求時發生錯誤時被呼叫

過濾器執行的順序是通過filterOrder方法進行排序,越小的值越優先處理。FilterConstants定義了一些列預設的過濾器的執行順序和路由型別,大部分需要用到的常量都在這兒。

例子中說明的,只有登入介面需要攔截,所以只需要攔截登入請求(/user/login)即可。可以通過過濾器的shouldFilter方法進行判斷是否需要攔截。

由於是在準發使用者服務成功後進行的資料修改,所以攔截器的型別時post型別的。整個類的實現如下:

public class AuthResponseFilter extends AbstractZuulFilter {

    private static final String RESPONSE_KEY_TOKEN = "token";
    @Value("${system.config.authFilter.authUrl}")
    private String authUrl;
    @Value("${system.config.authFilter.tokenKey}"
) private String tokenKey = RESPONSE_KEY_TOKEN; @Autowired private AuthApi authApi; @Override public boolean shouldFilter() { RequestContext context = getCurrentContext(); return StringUtils.equals(context.getRequest().getRequestURI().toString(), authUrl); } @Override public Object run() { try { RequestContext context = getCurrentContext(); InputStream stream = context.getResponseDataStream(); String body = StreamUtils.copyToString(stream, Charset.forName("UTF-8")); if (StringUtils.isNotBlank(body)) { Gson gson = new Gson(); @SuppressWarnings("unchecked") Map<String, String> result = gson.fromJson(body, Map.class); if (StringUtils.isNotBlank(result.get(tokenKey))) { AuthModel authResult = authApi.encodeToken(result.get(tokenKey)); if (authResult.getStatus() != HttpServletResponse.SC_OK) { throw new IllegalArgumentException(authResult.getErrMsg()); } String accessToken = authResult.getToken(); result.put(tokenKey, accessToken); } body = gson.toJson(result); } context.setResponseBody(body); } catch (IOException e) { rethrowRuntimeException(e); } return null; } @Override public String filterType() { return FilterConstants.POST_TYPE; } @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 2; } }

配置檔案,中新增授權url和返回token的key:
system.config.authFilter.authUrl=/user/login
system.config.authFilter.tokenKey=token
context.setResponseBody(body);這段程式碼是核心,通過此方法修改返回資料。

當用戶登入成功後,根據返回的token,通過授權服務進行token加密,這裡加密方式使用的是JWT。防止使用者篡改資訊,非法的請求直接可以攔截在閘道器層。

關於Zuul過濾器的執行過程,這裡不需要多說明,原始碼一看便知,ZuulServletFilter:

@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            try {
                preRouting();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }

            // Only forward onto to the chain if a zuul response is not being sent
            if (!RequestContext.getCurrentContext().sendZuulResponse()) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }

            try {
                routing();
            } catch (ZuulException e) {
                error(e);
                postRouting();
                return;
            }
            try {
                postRouting();
            } catch (ZuulException e) {
                error(e);
                return;
            }
        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }

方法說明:
preRoute:執行pre型別的過濾器
postRoute:執行post型別的過濾器
route:執行route型別的過濾器
error:執行error型別的過濾器

通過context.setSendZuulResponse(false)可以終止請求的轉發,但是隻在pre型別的過濾器中設定才可以。

關於如何終止過濾器:
只有pre型別的過濾器支援終止轉發,其他過濾器都是按照順序執行的,而且pre型別的過濾器也只有在所有pre過濾器執行完後才可以終止轉發,做不到終止過濾器繼續執行。看ZuulServletFilter原始碼程式碼:

     // Only forward onto to the chain if a zuul response is not being sent
            if (!RequestContext.getCurrentContext().sendZuulResponse()) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }

本文中的程式碼已提交至: https://gitee.com/cmlbeliever/springcloud 歡迎Star
實現類在:api-getway工程下的com.cml.springcloud.api.filter.AuthResponseFilter