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. 感悟
- 從問題的實際情況出發,而不是從我們自以為的解決方案出發。我們的目的在於更快更好地解決當下的問題,而不是都一大圈子回來,做很多無謂的工作。
- 大道至簡,往往最簡單的配置,最簡單的代碼,就可以實現我們想要的復雜的功能點。莫要鑽進牛角尖自以為是出不來。
- 工作才是最好的老師,學中用,用中學,方能不斷進步。
- 解決問題也不一定需要親力親為,在企業內部,有專門的中間件團隊和流程規范來保證着專業的人,做專業的事,做正確的事。多交流遠比自己琢磨要好得多。