Spring Cloud實戰小貼士:Zuul處理Cookie和重定向

https://mp.weixin.qq.com/s/oDIFsfQpApvh-wUL7eE8Fw瀏覽器

因爲咱們在以前全部的入門教程中,對於HTTP請求都採用了簡單的接口實現。而實際使用過程當中,咱們的HTTP請求要複雜的多,好比當咱們將Spring Cloud Zuul做爲API網關接入網站類應用時,每每都會碰到下面這兩個很是常見的問題:微信

  • 會話沒法保持
  • 重定向後的HOST錯誤

本文將幫助你們分析問題緣由並給出解決這兩個常見問題的方法。ide

會話保持問題

經過跟蹤一個HTTP請求通過Zuul到具體服務,再到返回結果的全過程。咱們很容易就能發現,在傳遞的過程當中,HTTP請求頭信息中的Cookie和Authorization都沒有被正確地傳遞給具體服務,因此最終致使會話狀態沒有獲得保持的現象。
那麼這些信息是在哪裏丟失的呢?咱們從Zuul進行路由轉發的過濾器做爲起點,來一探究竟。下面是RibbonRoutingFilter過濾器的實現片斷:函數

public class RibbonRoutingFilter extends ZuulFilter{
    ...
    protected ProxyRequestHelper helper;

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders();
        try {
            RibbonCommandContext commandContext = buildCommandContext(context);
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        ...
        return null;
    }

        protected RibbonCommandContext buildCommandContext(RequestContext context) {
        HttpServletRequest request = context.getRequest();
        MultiValueMap<String, String> headers = this.helper
                .buildZuulRequestHeaders(request);
        MultiValueMap<String, String> params = this.helper
                .buildZuulRequestQueryParams(request);
        ...
    }
}

這裏有三個重要元素:網站

  • 過濾器的核心邏輯run函數實現,其中調用了內部函數buildCommandContext來構建上下文內容
  • 而buildCommandContext中調用了helper對象的buildZuulRequestHeaders方法來處理請求頭信息
  • helper對象是ProxyRequestHelper類的實例

接下來咱們再看看ProxyRequestHelper的實現:ui

public class ProxyRequestHelper {
    public MultiValueMap<String, String> buildZuulRequestHeaders(
            HttpServletRequest request) {
        RequestContext context = RequestContext.getCurrentContext();
        MultiValueMap<String, String> headers = new HttpHeaders();
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                if (isIncludedHeader(name)) {
                    Enumeration<String> values = request.getHeaders(name);
                    while (values.hasMoreElements()) {
                        String value = values.nextElement();
                        headers.add(name, value);
                    }
                }
            }
        }
        Map<String, String> zuulRequestHeaders = context.getZuulRequestHeaders();
        for (String header : zuulRequestHeaders.keySet()) {
            headers.set(header, zuulRequestHeaders.get(header));
        }
        headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip");
        return headers;
    }
    public boolean isIncludedHeader(String headerName) {
        String name = headerName.toLowerCase();
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey(IGNORED_HEADERS)) {
            Object object = ctx.get(IGNORED_HEADERS);
            if (object instanceof Collection && ((Collection<?>) object).contains(name)) {
                return false;
            }
        }
        ...
    }
}

從上述源碼中,咱們能夠看到構建頭信息的方法buildZuulRequestHeaders經過isIncludedHeader函數來判斷當前請求的各個頭信息是否在忽略的頭信息清單中,若是是的話就不組織到這次轉發的請求中去。那麼這些須要忽略的頭信息是在哪裏初始化的呢?在PRE階段的PreDecorationFilter過濾器中,咱們能夠找到答案:this

public class PreDecorationFilter extends ZuulFilter{
    ...
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
        Route route = this.routeLocator.getMatchingRoute(requestURI);
        if (route != null) {
            String location = route.getLocation();
            if (location != null) {
                ctx.put("requestURI", route.getPath());
                ctx.put("proxy", route.getId());
                // 處理忽略頭信息的部分
                if (!route.isCustomSensitiveHeaders()) {
                    this.proxyRequestHelper.addIgnoredHeaders(
                        this.properties.getSensitiveHeaders()
                        .toArray(new String[0]));
                } else {
                    this.proxyRequestHelper.addIgnoredHeaders(
                        route.getSensitiveHeaders()
                        .toArray(new String[0]));
                }
        ...
}

從上述源碼中,咱們能夠看到有一段if/else塊,經過調用ProxyRequestHelper的addIgnoredHeaders方法來添加須要忽略的信息到請求上下文中,供後續ROUTE階段的過濾器使用。這裏的if/else塊分別用來處理全局設置的敏感頭信息和指定路由設置的敏感頭信息。而全局的敏感頭信息定義於ZuulProperties中:url

1
2
3
4
5
6
7
@Data
@ConfigurationProperties("zuul")
public class ZuulProperties{
    private Set<String> sensitiveHeaders = new LinkedHashSet<>(
            Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
    ...
}

因此解決該問題的思路也很簡單,咱們只須要經過設置sensitiveHeaders便可,設置方法分爲兩種:code

  • 全局設置:
  • zuul.sensitive-headers=
  • 指定路由設置:
  • zuul.routes.<routeName>.sensitive-headers=
  • zuul.routes.<routeName>.custom-sensitive-headers=true

重定向問題

在使用Spring Cloud Zuul對接Web網站的時候,處理完了會話控制問題以後。每每咱們還會碰到以下圖所示的問題,咱們在瀏覽器中經過Zuul發起了登陸請求,該請求會被路由到某WebSite服務,該服務在完成了登陸處理以後,會進行重定向到某個主頁或歡迎頁面。此時,仔細的開發者會發現,在登陸完成以後,咱們瀏覽器中URL的HOST部分發生的改變,該地址變成了具體WebSite服務的地址了。這就是在這一節,咱們將分析和解決的重定向問題!
Spring Cloud實戰小貼士:Zuul處理Cookie和重定向對象

出現該問題的根源是Spring Cloud Zuul沒有正確的處理HTTP請求頭信息中的Host致使。在Brixton版本中,Spring Cloud Zuul的PreDecorationFilter過濾器實現時徹底沒有考慮這一問題,它更多的定位於REST API的網關。因此若是要在Brixton版本中增長這一特性就相對較爲複雜,不過好在Camden版本以後,Spring Cloud Netflix 1.2.x版本的Zuul加強了該功能,咱們只須要經過配置屬性zuul.add-host-header=true就能讓本來有問題的重定向操做獲得正確的處理。關於更多Host頭信息的處理,讀者能夠參考本文以前的分析思路,能夠經過查看PreDecorationFilter過濾器的源碼來詳細更多實現細節。

SpringCloud新書推薦

Spring Cloud實戰小貼士:Zuul處理Cookie和重定向
Spring Cloud實戰小貼士:Zuul處理Cookie和重定向
版權聲明

本文采用 CC BY 3.0 CN協議 進行許可。 可自由轉載、引用,但需署名做者且註明文章出處。如轉載至微信公衆號,請在文末添加做者公衆號二維碼。
長按指紋
一鍵關注
Spring Cloud實戰小貼士:Zuul處理Cookie和重定向
Spring Cloud實戰小貼士:Zuul處理Cookie和重定向

相關文章
相關標籤/搜索