专栏首页软件开发-青出于蓝Springcloud之zuul的过滤头部

Springcloud之zuul的过滤头部

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

    在使用zuul时,我有俩个需求,一是不让zuul过滤头部的Cookie,二是要在zuul网关对request的header设置requestId——便于链路追踪。

    在网上搜了下,发现sensitiveHeaders和ignoredHeaders描述的较多,但是大多描述的不是较详细,同时自己也想知道底层上是如何做的,所以看了下源码,记录下。如下List-1,在application.yml中设置zuul的sensitiveHeaders和ignoredHeaders,先来说结论,sensitiveHeaders的值设置为x1后servletRequest header的x1不会传到下游;ignoredHeaders的值设置为x2后,servletRequest header的x2不会传到下游。

List-1

zuul:
  sensitiveHeaders: x1
  ignoredHeaders: x2

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

List-2

@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,

List-3

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-4

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所示。

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. 源码

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringFramework之ViewResolver优化

    Springboot中,对mvc进行自动化配置时在WebMvcAutoConfiguration中会自动注入InternalResourceViewResolv...

    克虏伯
  • Springcloud之zuul过滤下游服务返回的header

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

    克虏伯
  • SpringFramework之RequestBodyAdvice的使用

    有人用RequestBodyAdvice来做参数的解密(前端传过来的是加密的),或者使用RequestBodyAdvice进行全局统一返回,但是我的需求是只对J...

    克虏伯
  • Java初始化List的6种方式

    这种就是我们平常用的最多最平常的方式了,没什么好说的,后面缺失的泛型类型在 JDK 7 之后就可以不用写具体的类型了,改进后会自动推断类型。

    诺浅
  • Java 中初始化 List 集合的 6 种方式!

    List 是 Java 开发中经常会使用的集合,你们知道有哪些方式可以初始化一个 List 吗?这其中不缺乏一些坑,今天栈长我给大家一一普及一下。

    Java技术栈
  • QQ小程序支付

    首先是配置类,设置为包内访问权限,其实应该放于properties文件,或者直接配置在xml中,偷了个懒直接写在了代码中

    WindrunnerMax
  • Java——String类使用详解(实例化、字符串比较、匿名对象、两种实例化方法的区别)

    String类不是一个基本数据类型,它是一个类,这个类设计过程种加入了Java的特殊支持,其实例化形式有两种形式:

    Winter_world
  • 聊聊rocketmq的AclClientRPCHook

    rocketmq-remoting-4.5.2-sources.jar!/org/apache/rocketmq/remoting/RPCHook.java

    codecraft
  • Java做爬虫也很牛

    首先我们封装一个Http请求的工具类,用HttpURLConnection实现,当然你也可以用HttpClient, 或者直接用Jsoup来请求(下面会讲到Js...

    猿天地
  • httpclient爬虫爬取汉字拼音等信息

    下面是使用httpclient爬虫爬取某个网站的汉字相关信息的实践代码,中间遇到了一些字符格式的问题。之前被同事见过用html解析类来抓取页面信息,而不是像我现...

    FunTester

扫码关注云+社区

领取腾讯云代金券