SpringBoot 中对 Forwarded 请求头的处理
SpringBoot 中对 Forwarded 请求头的处理
然后是
SpringBoot 内嵌 Tomcat 的时候,可以通过 forward-headers-strategy 来判断 forward 请求头的处理方式,不同的配置有不同的结果,这个不同指的是什么
https://stackoverflow.com/questions/68318269/spring-server-forward-headers-strategy-native-vs-framework
在 ServletWebServerFactoryAutoConfiguration 自动配置类中,forwardedHeaderFilter 方法中我们可以看到,如果 forward-headers-strategy 配置为 FRAMEWORK,则会创建过滤器 ForwardedHeaderFilter,这个过滤器的作用是什么呢?
起作用很简单,就是从 Forwarded
和 X-Forwarded-*
报头中提取值,然后包装请求和响应,并在以下方法中起作用,使它们返回的信息,是客户端发起的那个请求和响应协议和目标 IP,也就是帮助我们剔除代理的影响,让我们可以正常获取发起请求的原始客户端的信息
实现方式,就是自定义了 HttpServletRequest 的实现为 ForwardedHeaderExtractingRequest
自定义了 HttpServletResponse 的实现为 RelativeRedirectResponseWrapper 或者 ForwardedHeaderExtractingResponse
做的事情很简单
HTTP headers 官方文档,以及 X-Forwarded-For 消息头
核心起作用的方法是 doFilterInternal
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (this.removeOnly) {
ForwardedHeaderRemovingRequest wrappedRequest = new ForwardedHeaderRemovingRequest(request);
filterChain.doFilter(wrappedRequest, response);
}
else {
HttpServletRequest wrappedRequest =
new ForwardedHeaderExtractingRequest(request);
HttpServletResponse wrappedResponse = this.relativeRedirects ?
RelativeRedirectResponseWrapper.wrapIfNecessary(response, HttpStatus.SEE_OTHER) :
new ForwardedHeaderExtractingResponse(response, wrappedRequest);
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
}
写个例子,用 nginx 转发试试,TODO
简单实践
刚搭建完的时候,nginx 没有添加 forward 相关请求头,所以 shouldNotFilter 方法返回 true,也就是不进行过滤,
此时,请求的来源地址,是 nginx 发起的请求,并不是客户端发起的请求,客户端发起的请求是 http://localhost:8081/proxy
{
"scheme": "http",
"requestURL": "http://localhost:8080/SpringBoot-ForwardHeader/target",
"contextPath": "/SpringBoot-ForwardHeader",
"serverName": "localhost",
"requestURI": "/SpringBoot-ForwardHeader/target",
"serverPort": 8080
}
下面我们来添加相关请求头,
http {
...
server {
listen 8081;
server_name localhost;
location /proxy {
proxy_pass http://localhost:8080/SpringBoot-ForwardHeader/target;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header X-Forwarded-Port $remote_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix $uri ;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
配置我们的 web 应用
server:
application:
name: ForwardHeader
# 当前应用所在的 Web 服务器监听的本地端口
port: 8080
servlet:
# 应用的上下文路径
context-path: /SpringBoot-ForwardHeader
# 启用默认的 Servlet
register-default-servlet: true
forward-headers-strategy: framework
此时,prefix 可以正常显示,
{
"scheme": "http",
"requestURL": "http://127.0.0.1:62031/proxy/target",
"contextPath": "/proxy",
"serverName": "127.0.0.1",
"requestURI": "/proxy/target",
"serverPort": 62031
}
手机 IP 为 192.168.43.68
{
"scheme": "http",
"requestURL": "http://192.168.43.68:48476/proxy/target",
"contextPath": "/proxy",
"serverName": "192.168.43.68",
"requestURI": "/proxy/target",
"serverPort": 48476
}
但是换成 native 之后,prefix 无法正常显示
server:
application:
name: ForwardHeader
# 当前应用所在的 Web 服务器监听的本地端口
port: 8080
servlet:
# 应用的上下文路径
context-path: /SpringBoot-ForwardHeader
# 启用默认的 Servlet
register-default-servlet: true
forward-headers-strategy: native
{
"scheme": "http",
"requestURL": "http://127.0.0.1:61876/SpringBoot-ForwardHeader/target",
"contextPath": "/SpringBoot-ForwardHeader",
"serverName": "127.0.0.1",
"requestURI": "/SpringBoot-ForwardHeader/target",
"serverPort": 61876
}
手机
{
"scheme": "http",
"requestURL": "http://192.168.43.68:48092/SpringBoot-ForwardHeader/target",
"contextPath": "/SpringBoot-ForwardHeader",
"serverName": "192.168.43.68",
"requestURI": "/SpringBoot-ForwardHeader/target",
"serverPort": 48092
}
none
{
"scheme": "http",
"requestURL": "http://192.168.43.42/SpringBoot-ForwardHeader/target",
"contextPath": "/SpringBoot-ForwardHeader",
"serverName": "192.168.43.42",
"requestURI": "/SpringBoot-ForwardHeader/target",
"serverPort": 80
}
可以看到没有一点转发头的影子,
注意在这个过滤器中,会清除转发(forward)相关的请求头,
forward-headers-strategy 默认是 none,不对 forward 请求头进行处理,TODO,
对比一下这两者的区别,native 和 none 的区别
研究结束,TODO,用手机看更明显,因此,我们再次回到我们的原点,这个配置是干什么的,这个配置是帮助我们剔除代理的影响,让我们可以正常获取发起请求的原始客户端的信息。
这个配置项,过滤器,本质上就是在捕捉 http 消息头,
所以只要一个工具可以添加相关请求头,就可以模拟 http 转发时 springboot 的处理。并不一定非得用 NGINX,用 postman 构造相关请求头也是可以的。