Spring Cloud combat tips: Zuul handles cookies and redirects

Since we have used a simple interface implementation for HTTP requests in all the previous introductory tutorials. In actual use, our HTTP requests are much more complicated. For example, when we use Spring Cloud Zuul as an API gateway to access website applications, we often encounter the following two very common problems:

  • Session cannot be maintained
  • HOST error after redirection

This article will help you analyze the cause of the problem and give solutions to these two common problems.

HTTP

1. The problem of session retention

By tracking an HTTP request through Zuul to a specific service, and then to the whole process of returning the result. We can easily find that in the process of transmission, the Cookie and Authorization in the HTTP request header information are not correctly passed to the specific service, so the phenomenon that the session state is not maintained eventually.

So where is this information lost? Let's take a look at Zuul's routing and forwarding filters as a starting point. The following is a snippet of the implementation of the RibbonRoutingFilter filter:

  1. public class RibbonRoutingFilter extends ZuulFilter{ 
  2.     ... 
  3.     protected ProxyRequestHelper helper; 
  4.      
  5.     @Override 
  6.     public Object run() { 
  7.         RequestContext context = RequestContext.getCurrentContext(); 
  8.         this.helper.addIgnoredHeaders (); 
  9.         try { 
  10.             RibbonCommandContext commandContext = buildCommandContext(context); 
  11.             ClientHttpResponse response = forward(commandContext); 
  12.             setResponse(response); 
  13.             return response; 
  14.         } 
  15.         ... 
  16.         return null; 
  17.     } 
  18.      
  19.         protected RibbonCommandContext buildCommandContext(RequestContext context) { 
  20.         HttpServletRequest request = context.getRequest(); 
  21.         MultiValueMap<String, String>headers = this.helper  
  22.                 .buildZuulRequestHeaders(request); 
  23.         MultiValueMap<String, String>params = this.helper  
  24.                 .buildZuulRequestQueryParams(request); 
  25.         ... 
  26.     } 

There are three important elements here:

  • The core logic run function implementation of the filter, which calls the internal function buildCommandContext to build the context content
  • The buildZuulRequestHeaders method of the helper object is called in the buildCommandContext to process the request header information
  • The helper object is an instance of the ProxyRequestHelper class

Next, let's look at the implementation of ProxyRequestHelper:

  1. public class ProxyRequestHelper { 
  2.     public MultiValueMap<String, String> buildZuulRequestHeaders( 
  3.             HttpServletRequest request) { 
  4.         RequestContext context = RequestContext.getCurrentContext(); 
  5.         MultiValueMap<String, String>headers = new HttpHeaders();  
  6.         Enumeration<String>headerNames = request.getHeaderNames();  
  7.         if (headerNames != null) { 
  8.             while (headerNames.hasMoreElements()) { 
  9.                 String name = headerNames.nextElement(); 
  10.                 if (isIncludedHeader(name)) { 
  11.                     Enumeration<String>values = request.getHeaders(name);  
  12.                     while (values.hasMoreElements()) { 
  13.                         String value = values.nextElement(); 
  14.                         headers.add(name, value); 
  15.                     } 
  16.                 } 
  17.             } 
  18.         } 
  19.         Map<String, String>zuulRequestHeaders = context.getZuulRequestHeaders();  
  20.         for (String header : zuulRequestHeaders.keySet()) { 
  21.             headers.set(header, zuulRequestHeaders.get(header)); 
  22.         } 
  23.         headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip"); 
  24.         return headers; 
  25.     } 
  26.     public boolean isIncludedHeader(String headerName) { 
  27.         String name = headerName.toLowerCase(); 
  28.         RequestContext ctx = RequestContext.getCurrentContext(); 
  29.         if (ctx.containsKey(IGNORED_HEADERS)) { 
  30.             Object object = ctx.get(IGNORED_HEADERS); 
  31.             if (object instanceof Collection && ((Collection<?>) object).contains(name)) { 
  32.                 return false; 
  33.             } 
  34.         } 
  35.         ... 
  36.     } 

From the above source code, we can see that buildZuulRequestHeaders, the method of constructing header information, uses the isIncludedHeader function to determine whether each header information of the current request is in the list of ignored header information. If so, it will not be organized into the forwarded request. So where are these ignored headers initialized? In the PreDecorationFilter filter of the PRE stage, we can find the answer:

  1. public class PreDecorationFilter extends ZuulFilter{ 
  2.     ... 
  3.     public Object run() { 
  4.         RequestContext ctx = RequestContext.getCurrentContext(); 
  5.         final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest()); 
  6.         Route route = this.routeLocator.getMatchingRoute(requestURI); 
  7.         if (route != null) { 
  8.             String location = route.getLocation(); 
  9.             if (location != null) { 
  10.                 ctx.put("requestURI", route.getPath()); 
  11.                 ctx.put("proxy", route.getId()); 
  12.                 // handle the part that ignores the header information 
  13.                 if (!route.isCustomSensitiveHeaders()) { 
  14.                     this.proxyRequestHelper.addIgnoredHeaders ( 
  15.                         this.properties.getSensitiveHeaders() 
  16.                         .toArray(new String[0])); 
  17.                 } else { 
  18.                     this.proxyRequestHelper.addIgnoredHeaders ( 
  19.                         route.getSensitiveHeaders() 
  20.                         .toArray(new String[0])); 
  21.                 } 
  22.         ... 

From the above source code, we can see that there is an if/else block that calls the addIgnoredHeaders method of ProxyRequestHelper to add the information that needs to be ignored into the request context for use by the filter in the subsequent ROUTE stage. The if/else blocks here are used to deal with the sensitive header information set globally and the sensitive header information set by the specified route respectively. The global sensitive header information is defined in ZuulProperties:

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

So the idea of ​​​​solving this problem is also very simple, we only need to set sensitiveHeaders, there are two setting methods:

1. Global settings:

  • zuul.sensitive-headers=

2. Specify the routing settings:

  • zuul.routes..sensitive-headers=
  • zuul.routes..custom-sensitive-headers=true

The problem of redirection

When using Spring Cloud Zuul to connect to the Web site, after dealing with the issue of session control. We often encounter the problem shown in the figure below. We initiate a login request through Zuul in the browser, and the request will be routed to a WebSite service. After the service completes the login process, it will be redirected to a certain website. a home page or welcome page. At this point, careful developers will find that after the login is completed, the HOST part of the URL in our browser has changed, and the address has become the address of the specific WebSite service. That's the redirection problem we'll analyze and solve in this section!

Spring Cloud Zuul docking web site

The root cause of this problem is that Spring Cloud Zuul does not properly handle the Host in the HTTP request header. In the Brixton version, Spring Cloud Zuul's PreDecorationFilter filter implementation does not consider this issue at all, and it is more positioned as a gateway to the REST API. So it is relatively complicated to add this feature to the Brixton version, but fortunately after the Camden version, the Spring Cloud Netflix 1.2.x version of Zuul has enhanced this feature, we only need to configure the property zuul.add-host-header =true will allow the problematic redirection to be handled correctly. For the processing of more Host header information, readers can refer to the analysis ideas before this article, and you can check the source code of the PreDecorationFilter filter for more implementation details.

 

 

Default filtered headers

spring-cloud-netflix-core-1.2.6.RELEASE-sources.jar!/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java


/**
     * List of sensitive headers that are not passed to downstream requests. Defaults to a
     * "safe" set of headers that commonly contain user credentials. It's OK to remove
     * those from the list if the downstream service is part of the same system as the
     * proxy, so they are sharing authentication data. If using a physical URL outside
     * your own domain, then generally it would be a bad idea to leak user credentials.
     */
    private Set<String> sensitiveHeaders = new LinkedHashSet<>(
            Arrays.asList("Cookie", "Set-Cookie", "Authorization"));

configure

zuul:
  sensitiveHeaders:  
  host:
    socket-timeout-millis: 60000
    connect-timeout-millis: 60000  

By showing that the designation is empty, it means that the filter header list of zuul is empty, so that it can return normally.

spring read cookie method

String xxx = WebUtils.getCookie((HttpServletRequest) servletRequest,"your-cookie-name").getValue();

 

http://zhuanlan.51cto.com/art/201705/538644.htm

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326994460&siteId=291194637