开发者社区> 程序猿DD> 正文

Spring Cloud实战小贴士:Zuul处理Cookie和重定向

简介: 由于我们在之前所有的入门教程中,对于HTTP请求都采用了简单的接口实现。而实际使用过程中,我们的HTTP请求要复杂的多,比如当我们将Spring Cloud Zuul作为API网关接入网站类应用时,往往都会碰到下面这两个非常常见的问题: 会话无法保持 重定向后的HOST错误 本文将帮助大家分析问题原因并给出解决这两个常见问题的方法。
+关注继续查看

由于我们在之前所有的入门教程中,对于HTTP请求都采用了简单的接口实现。而实际使用过程中,我们的HTTP请求要复杂的多,比如当我们将Spring Cloud Zuul作为API网关接入网站类应用时,往往都会碰到下面这两个非常常见的问题:

  • 会话无法保持
  • 重定向后的HOST错误

本文将帮助大家分析问题原因并给出解决这两个常见问题的方法。

会话保持问题

通过跟踪一个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的实现:

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过滤器中,我们可以找到答案:

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块,通过调用ProxyRequestHelperaddIgnoredHeaders方法来添加需要忽略的信息到请求上下文中,供后续ROUTE阶段的过滤器使用。这里的if/else块分别用来处理全局设置的敏感头信息和指定路由设置的敏感头信息。而全局的敏感头信息定义于ZuulProperties中:

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

所以解决该问题的思路也很简单,我们只需要通过设置sensitiveHeaders即可,设置方法分为两种:

  • 全局设置:
    • 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没有正确的处理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过滤器的源码来详细更多实现细节。

本文由 程序猿DD-翟永超 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。
原文首发于:http://blog.didispace.com/spring-cloud-zuul-cookie-redirect/

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
构建在线教育弹性高可用视频处理架构实战
对于负责建设视频处理系统的技术团队而言,这样的业务场景就留给了他们一系列的挑战。
1287 0
Python系列直播——深入Python与日志服务,玩转大规模数据分析处理实战
Python系列直播——深入Python与日志服务,玩转大规模数据分析处理实战
4931 0
【Web API系列教程】3.9 — 实战:处理数据(添加新条目到数据库)
在本节,你将添加让用户可以创建新book的功能。在app.js中,添加如下代码到视图模型: self.authors = ko.
810 0
【Web API系列教程】3.8 — 实战:处理数据(显示条目细节)
在本节,你将添加查看每本书的详细信息的功能。在app.js中,添加以下代码到视图模型: self.detail = ko.
572 0
【Web API系列教程】3.7 — 实战:处理数据(创建UI视图)
在本节,你将开始为app定义HTML,并在HTML和视图模型间添加数据绑定。 打开Views/Home/Index.cshtml文件。
699 0
Matplotlib中的事件处理详解与实战
Matplotlib是Python的绘图库,可以生成所需的出版质量级别的图形,本文将详细介绍Matplotlib中的事件处理,并通过实战了解其用法。
1073 0
JSP和Servlet里的Cookie处理
JSP和Servlet里的Cookie处理
12 0
***php(codeigniter)中如何重定向
Q: 在保存完数据之后需要重定向,防止数据重复提交。 我使用$this->方法名();跳转,发现不能达到重定向的效果(地址栏没变) 请教高手重定向怎么用 A: $this->load->helper('url');redirect('/login/form/', 'refresh'); 通过发送HTTP头,命令客户端转向到您指定的URL。
515 0
+关注
程序猿DD
公众号:程序猿DD 《Spring Cloud微服务实战》作者 didispace.com博主 spring4all.com发起人
537
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载