1. 缘由
最近单位里面调整应用端口和防火墙配置,原先起在 80 端口上的服务,需要调整到 8180 端口上进行部署。为了对公网的影响度最小,公网端口依旧采用的 80 端口暴露服务,由网络的同事在 WAF 侧做了一个 80 到 8180 端口的映射。形成了一个这样的网络拓扑(脱敏简化)图。
形成这样的网络拓扑之后,按理公网上访问 http://test.snowheart.cn/ 时即可正常访问到后台的应用服务。But,后端有一些地方用到了 sendRedirect(url) 方法,这就导致了在进行 302 网络跳转的时候,Response 的 Location Header 上面带了内部的端口,即形成了下述形式的 Location Header。
HTTP/1.1 302 Moved Temporarily
Date: Mon, 11 May 2020 15:45:29 GMT
Location: http://test.snowheart.cn:8180/app/login.jsp
Content-Type: text/html;charset=utf-8
Connection: close
Transfer-Encoding: chunked
浏览器在进行跳转时,使用了前面一个请求的 Location,在公网上请求了 8180 端口,因为防火墙的缘故,无法访问,报错。
2. 问题分析
2.1. 浏览器的重定向跳转机制
在 HTTP 的 Code 响应码中,301 表示永久重定向,302 表示临时重定向。
-
通过 Response 中的 HTTP Code,浏览器可以得知接下来要进行再次请求跳转了。
-
通过 Response 中的 Location,浏览器可以得知下一次要请求到哪里。
// DemoServlet1
// 通过设置 Header 和 Status,来进行重定向操作,其他什么都不做
response.setHeader("Location", request.getContextPath() + "/hello.html");
response.setStatus(302);
上述例子中的 Servlet URL,在浏览器中访问之后,你会发现浏览器被重定向到了项目中的 /hello.html,其代码在实现结果上,等价于下面的代码:
// DemoServlet2
// 通过 sendRedirect(url) 来进行重定向操作,其他什么都不做
response.sendRedirect(request.getContextPath() + "/hello.html");
2.2. 8180 端口是在哪里添加的?
如果 WAF 上是同端口透明出去了,公网和内网前置机的 Port 一致;
链路上除前置机应用端口为 8180 外,其余地方端口均与此无关;
只是依赖 Apache 或 Nginx 的反向代理,是不会出现后端 Port 暴露的事情;
看来是在 WAF 做了端口转换之后,内网前置机的 Apache 并不知道公网上的端口已经是 80 了,故在 Location 上增加了 8180 端口的信息;
亦可通过在各环节打印日志的形式,来检测 8180 端口是哪个环节增加的...
故:我们判定,在从前置机 Apache 返回时,Location 上就已经有了前面的域名 + 端口,现在需要做的事情就是在返回之前,将 Location 上的端口给抹掉,亦或者是将 Location 上的域名 + 端口给抹掉,仅剩余从 / 开始的 URI 地址。
3. 改造实施
最先时候,想通过自己最熟悉的 Nginx 来进行 HTTP Response Header 的重写
3.1. Nginx 配置
-
在 Linux 虚机上安装 Nginx 并进行测试工作(已有 Nginx 跳过)
Windows 下的 Nginx 程序包无法进行再次编译,没有办法来加载其他 Nginx 模块;故需要使用 Linux 下的 Nginx 服务,以最大限度地与服务器保持一致。
-
查询 Nginx 编译参数,安装 ngx_headers_more 模块,重新编译并覆盖 nginx 文件
# 下载最新的 ngx_headers_more 模块包
wget https://github.com/openresty/headers-more-nginx-module/archive/v0.33.tar.gz
tar -xzvf v0.33.tar.gz
# 跳转到 Nginx 目录
# 查看 Nginx 编译参数
./nginx -V
# 跳转至 Nginx 源码目录,增加 ngx_headers_more 配置并编译
# 每个人的目录都不相同,仅供参考
./configure --prefix=/usr/local/nginx/server --with-http_stub_status_module --with-http_ssl_module --with-pcre=/tool/pcre-8.44 --with-http_realip_module --with-http_sub_module --with-http_gzip_static_module --add-module=/tool/echo-nginx-module-0.62rc1 --add-module=/tool/headers-more-nginx-module-0.33
make
# 切记,不要 make install,否则会覆盖掉之前Nginx的配置文件这些。
cp /usr/local/nginx/server/sbin/nginx /usr/local/nginx/server/sbin/nginx.origin
cp objs/nginx /usr/local/nginx/server/sbin/nginx
- 修改配置文件 nginx.conf
map $upstream_http_Location $location{
# 这种方案是把所有到 8180 端口的重定向都改成 80 端口,有一定的风险,容易误伤
# ~http://(?<domains>.*):8180/(?<param>.*) http://$domains/$param;
# 这种方案是针对特定域名 8180 端口的重定向,范围可控,写法冗长
~http://test.snowheart.cn:8180/(?<param>.*) http://test.snowheart.cn/$param;
# 默认情况,保持原状
default $upstream_http_Location;
}
server {
location /app {
proxy_pass http://192.168.36.72:7001/app;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
more_set_headers -s '301 302' 'Location $location';
}
}
- 重启应用,进行测试
$ curl http://test.snowheart.cn/app/RedirectServlet --dump -
HTTP/1.1 302
Server: nginx/1.14.2
Date: Mon, 11 May 2020 16:41:23 GMT
Content-Length: 0
Location: http://test.snowheart.cn/app/hello.html
Connection: keep-alive
大坑
Nginx 需要安装新的 module,这又涉及到重新编译和 root 安装等操作,在企业里面,做这些是需要一定的流程和规范性评估的,故经过考虑后,放弃此方案。上述笔记仅作为 POC 阶段使用。
Nginx 方案受阻后,使用当前测试环境在用的 Apache 来实现 Location 的重写
3.2. Apache 配置
- 配置文件
# httpd.conf
# 打开 Rewrite 功能
RewriteEngine On
# 对 Location 进行重写
Header edit Location "(^http[s]?://test.snowheart.cn:8180/)" "/"
Header edit Location "(^http[s]?://123.234.123.234:8180/)" "/"
- 测试应用
$ curl http://test.snowheart.cn/app/RedirectServlet --dump -
HTTP/1.1 302
Server: nginx/1.14.2
Date: Mon, 11 May 2020 16:41:23 GMT
Content-Length: 0
Location: http://test.snowheart.cn/app/hello.html
Connection: keep-alive
简单的三行配置,即达成了我们的目的。
4. 感悟
- 从问题的实际情况出发,而不是从我们自以为的解决方案出发。我们的目的在于更快更好地解决当下的问题,而不是都一大圈子回来,做很多无谓的工作。
- 大道至简,往往最简单的配置,最简单的代码,就可以实现我们想要的复杂的功能点。莫要钻进牛角尖自以为是出不来。
- 工作才是最好的老师,学中用,用中学,方能不断进步。
- 解决问题也不一定需要亲力亲为,在企业内部,有专门的中间件团队和流程规范来保证着专业的人,做专业的事,做正确的事。多交流远比自己琢磨要好得多。
5. 参考链接
本文由博客群发一文多发等运营工具平台 OpenWrite 发布