1. HTTPS无法正确获取

  2. 代理端口变化后无法正确获取

  3. url截取后无法正确获取uri

1. 相关资料

X-Forwarded-For (XFF)::
X-Forwarded-For: <client>, <proxy1>, <proxy2>
X-Forwarded-Port
X-Forwarded-Port: <port>

2. 问题原因解析

org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java
private void addProxyHeaders(RequestContext ctx, Route route) {
    HttpServletRequest request = ctx.getRequest();
    String host = toHostHeader(request);
    String port = String.valueOf(request.getServerPort());
    String proto = request.getScheme();
    if (hasHeader(request, "X-Forwarded-Host")) {
        host = request.getHeader("X-Forwarded-Host") + "," + host;
        if (!hasHeader(request, "X-Forwarded-Port")) {
            if (hasHeader(request, "X-Forwarded-Proto")) {
                StringBuilder builder = new StringBuilder();
                for (String previous : StringUtils.commaDelimitedListToStringArray(request.getHeader("X-Forwarded-Proto"))) {
                    if (builder.length()>0) {
                        builder.append(",");
                    }
                    builder.append("https".equals(previous) ? "443" : "80");
                }
                builder.append(",").append(port);
                port = builder.toString();
            }
        } else {
            port = request.getHeader("X-Forwarded-Port") + "," + port;
        }
        proto = request.getHeader("X-Forwarded-Proto") + "," + proto;
    }
    ctx.addZuulRequestHeader("X-Forwarded-Host", host);
    ctx.addZuulRequestHeader("X-Forwarded-Port", port);
    ctx.addZuulRequestHeader(ZuulHeaders.X_FORWARDED_PROTO, proto);
    addProxyPrefix(ctx, route);
}

org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer.java类中定制:

org.apache.catalina.valves.RemoteIpValve.java
public void invoke(Request request, Response response) throws IOException, ServletException {
//...
	if (protocolHeader != null) {
	    String protocolHeaderValue = request.getHeader(protocolHeader);
	    if (protocolHeaderValue == null) {
	        // Don't modify the secure, scheme and serverPort attributes
	        // of the request
	    } else if (isForwardedProtoHeaderValueSecure(protocolHeaderValue)) {
	        request.setSecure(true);
	        request.getCoyoteRequest().scheme().setString("https");
	        setPorts(request, httpsServerPort);
	    } else {
	        request.setSecure(false);
	        request.getCoyoteRequest().scheme().setString("http");
	        setPorts(request, httpServerPort);
	    }
	}
//...
}
private boolean isForwardedProtoHeaderValueSecure(String protocolHeaderValue) {
    if (!protocolHeaderValue.contains(",")) {
        return protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue);
    }
    String[] forwardedProtocols = commaDelimitedListToStringArray(protocolHeaderValue);
    if (forwardedProtocols.length == 0) {
        return false;
    }
    for (int i = 0; i < forwardedProtocols.length; i++) {
        if (!protocolHeaderHttpsValue.equalsIgnoreCase(forwardedProtocols[i])) {
            return false;
        }
    }
    return true;
}

3. 解决方案

3.1. Spring过滤器来改写

启用:ForwardedHeaderFilter(注:spring默认不开启)

server:
  forward-headers-strategy: framework

或者

@Bean
FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter()
{
    FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>();
    bean.setFilter(new ForwardedHeaderFilter());
    return bean;
}

在最外层nginx配置信任边界代理

proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Prefix /gateway/; # 如有url改写需配置

3.2. 通过增加独立Head变量传递

  1. 最外层Nginx

    proxy_set_header Host $host:$server_port;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Url-Scheme $scheme;
    proxy_set_header X-Url-Port $server_port;
    proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
    proxy_set_header X-Forwarded-Ssl on;
  2. 非最外层Nginx

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
    proxy_set_header X-Url-Scheme $http_x_forwarded_proto;
    proxy_set_header X-Url-Port $http_x_forwarded_port;;
    proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
    proxy_set_header X-Forwarded-Prefix $http_x_forwarded_prefix; # 如有url改写需配置
    proxy_set_header X-Forwarded-Ssl on;
  3. SpringBoot

    server:
      tomcat:
        protocol-header: X-Url-Scheme
        port-header: X-Url-Port

3.3. ForwardedHeaderFilter无法解决nginx截断url的问题

改造ForwardedHeaderFilter
  1. uri改写:原逻辑为:<prefix-path>/<PathWithinApplication>,新逻辑为:<prefix-path>/<servlet-context-path>/<PathWithinApplication>

  2. contextPath改写:原逻辑为:<servlet-context-path>,新逻辑为:<prefix-path>/<servlet-context-path>

ForwardedHeaderFilter.ForwardedPrefixExtractor
@Nullable
private String initRequestUri() {
    if (this.forwardedRequestUri != null) {
        return this.forwardedRequestUri;
    }
    if (this.forwardedPrefix != null) {
        return this.forwardedPrefix + this.delegate.get().getContextPath() + this.pathHelper.getPathWithinApplication(this.delegate.get());
    }
    return null;
}

public String getContextPath() {
    return this.forwardedPrefix == null ? this.delegate.get().getContextPath() : this.forwardedPrefix + this.delegate.get().getContextPath();
}

4. Spring gateway

Spring gateway 会添加Forwarded,而 UriComponentsBuilder.adaptFromForwardedHeaders 方法会优先使用Forwarded,这样会导致nginx设置的 X-Forwarded-* 配置被忽略,解决办法,在nginx中也配置一下Forwarded。

proxy_set_header Forwarded 'proto=$scheme;host="$host:$server_port";for="$proxy_add_x_forwarded_for"';