Springcloud之zuul的過濾頭部

    Springcloud的版本是Greenwich.SR2,Springboot版本是2.1.6.release.java

    在使用zuul時,我有倆個需求,一是不讓zuul過濾頭部的Cookie,二是要在zuul網關對request的header設置requestId——便於鏈路追蹤。git

    在網上搜了下,發現sensitiveHeaders和ignoredHeaders描述的較多,可是大多描述的不是較詳細,同時本身也想知道底層上是如何作的,因此看了下源碼,記錄下。以下List-1,在application.yml中設置zuul的sensitiveHeaders和ignoredHeaders,先來講結論,sensitiveHeaders的值設置爲x1後servletRequest header的x1不會傳到下游;ignoredHeaders的值設置爲x2後,servletRequest header的x2不會傳到下游。github

    List-1spring

zuul:
  sensitiveHeaders: x1
  ignoredHeaders: x2

    以下List-2所示,List-1中的配置會被Spring解析到ZuulProperties,sensitiveHeaders是有默認值的,即Cookie、Set-Cookie、Authorization。bash

    List-2app

@ConfigurationProperties("zuul")
public class ZuulProperties {
    ...
    private Set<String> ignoredHeaders = new LinkedHashSet<>();
    ...
    private Set<String> sensitiveHeaders = new LinkedHashSet<>(
			Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
    ...

    來看PreDecorationFilter的run方法實現,以下List-3,1處和2處,會將ZuulProperties中sensitiveHeaders的值加入到要過濾的字段裏面,來看下ProxyRequestHelper的addIgnoredHeaders方法,以下List-4所示,RequestContext是個ConcurrentHashMap,ide

    List-3ui

public class PreDecorationFilter extends ZuulFilter {
    ...
	@Override
	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(REQUEST_URI_KEY, route.getPath());
				ctx.put(PROXY_KEY, route.getId());
				if (!route.isCustomSensitiveHeaders()) {
					this.proxyRequestHelper.addIgnoredHeaders(//1
							this.properties.getSensitiveHeaders().toArray(new String[0]));
				}
				else {
					this.proxyRequestHelper.addIgnoredHeaders(//2
							route.getSensitiveHeaders().toArray(new String[0]));
				}
        ...
	}

      List-4this

public void addIgnoredHeaders(String... names) {
    RequestContext ctx = RequestContext.getCurrentContext();
    if (!ctx.containsKey(IGNORED_HEADERS)) {
        ctx.set(IGNORED_HEADERS, new HashSet<String>());
    }
    @SuppressWarnings("unchecked")
    Set<String> set = (Set<String>) ctx.get(IGNORED_HEADERS);
    for (String name : this.ignoredHeaders) {
        set.add(name.toLowerCase());//1
    }
    for (String name : names) {
        set.add(name.toLowerCase());//2
    }
}
  1. List-4中,1處this.ignoredHeaders()會獲取ZuulProperties中的ignoredHeaders,以後加入到HashSet中。
  2. 2處,將方法上的參數值所有加入到HashSet中。
  3. 這樣,咱們設置的sensitiveHeaders和ignoredHeaders所有加到HashSet中,須要注意的是1處和2處都調用了toLowerCase()方法,因此下游收到的header中的字段key都是小寫的。

    來看RibbonRoutingFilter的run方法,以下List-5,run()調用buildCommandContext來構造RibbonCommand,buildCommandContext方法中調用了ProxyRequestHelper的buildZuulRequestHeaders方法,如List-6所示。url

    List-5

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

protected RibbonCommandContext buildCommandContext(RequestContext context) {
    HttpServletRequest request = context.getRequest();

    MultiValueMap<String, String> headers = this.helper
            .buildZuulRequestHeaders(request);
    ...
}

    List-6

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)) {//1
                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()) {
        if (isIncludedHeader(header)) {//2
            headers.set(header, zuulRequestHeaders.get(header));
        }
    }
    if (!headers.containsKey(HttpHeaders.ACCEPT_ENCODING)) {
        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;
        }
    }
    switch (name) {
    case "host":
        if (addHostHeader) {
            return true;
        }
    case "connection":
    case "content-length":
    case "server":
    case "transfer-encoding":
    case "x-application-context":
        return false;
    default:
        return true;
    }
}
  1. 獲取HttpServletRequest的header,以後遍歷,調用isIncludedHeader方法,isIncludedHeader裏面獲取RequestContext,判斷當前的這個header key是否是在IGNORED_HEADERS這個集合裏面——List-4中設置的,若是在裏面,那麼返回false,不會將這個header字段往下游傳。
  2. 2處,context.getZuulRequestHeaders()獲取咱們手動設置的header(調用addZuulResponseHeader方法設置),以後逐個遍歷,若是在IGNORED_HEADERS這個集合裏面——List-4中,則不會加入到headers中,即不往下游傳。

    注:鏈路爲何從PreDecorationFilter到RibbonRoutingFilter的,這和Zuul的內部的ZuulFilter機制有關。

    要注意的是,若是要往下游傳的header含有大寫的,那麼下游接收到的header是小寫的,緣由在List-4中能夠看出。

Reference

  1. 源碼
相關文章
相關標籤/搜索