Nginx以HTTP反向代理HTTPS服务
简单记录一下 Nginx 作为反向代理以 HTTP 协议向下游客户端代理使用 Spring Security 实现的上游 HTTPS 服务时遇到的问题及解决办法。
背景
有个基于 Spring Security、Spring MVC 实现的 HTTPS Web 应用,通过 Nginx 作为反向代理向外提供服务。
Nginx 和 Web 应用部署在同一台机器,IP 为 10.115.6.165。Web 应用以 HTTPS 协议监听在端口 19026。
如果是 Windows 环境,推荐在 http://nginx-win.ecsds.eu/ 下载编译带入了更多模块的 Nginx,因为在下面我们会需要 headers more 模块中的 more_set_headers 指令。如果是 Linux 环境,也请确认 headers more 模块的 more_set_headers 指令可用。
Nginx 以 HTTP 的方式反向代理。
用以下配置运行 Ngnix, 使其用 HTTP 协议在 9080 端口反向代理 19026 上的 HTTPS 服务。
server {
listen 9080;
server_name 10.115.6.165;
location /databoard/ {
proxy_pass https://10.115.6.165:19026/databoard/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
但是如果我们用浏览器访问http://10.115.6.165:9080/databoard/login,就会发现如下图所示的两问题:
1)后端服务使用 redirect 重定向导致的问题
map $upstream_http_Location $location {
~https://10.115.6.165/(?<param>.*) http://10.115.6.165:9080/$param;
default $upstream_http_Location;
}
server {
... ...
location /databoard/ {
... ...
more_set_headers -s '301 302' 'Location $location';
2)Cookie 携带 Secure 属性导致浏览器不能在请求中携带 SessionID 的问题。
有 Secure 属性的 Cookie 意味着如果浏览器不是用 HTTPS 与服务建立链接,那么这个 cookie 里的值不会随请求一起向服务器发送。所以要解决这个问题就需要在 Nginx 中把 cookie 中的 Secure 属性去掉再传给浏览器。解决办法如下:
map $sent_http_set_cookie $resp_cookie {
~*(?<CK_WITHOUT_SECURE>.+)Secure $CK_WITHOUT_SECURE;
}
server {
... ...
location /databoard/ {
... ...
more_set_headers 'Set-Cookie: $resp_cookie';
完整的相关配置
map $upstream_http_Location $location {
~https://10.115.6.165/(?<param>.*) http://10.115.6.165:9080/$param;
default $upstream_http_Location;
}
map $sent_http_set_cookie $resp_cookie {
~*(?<CK_WITHOUT_SECURE>.+)Secure $CK_WITHOUT_SECURE;
}
server {
listen 9080;
server_name 10.115.6.165;
location /databoard/ {
proxy_pass https://10.115.6.165:19026/databoard/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
more_set_headers -s '301 302' 'Location $location';
more_set_headers 'Set-Cookie: $resp_cookie';
}
}
Nginx 以 HTTPS 的方式反向代理。
如果 nginx 是以 HTTPS 协议向外提供反向代理,那么无论使用七层代理还是四层代理配置起来都很简单,如下:
3)四层反向代理。
stream {
upstream databoardServer {
hash $remote_addr consistent;
server 10.115.6.165:19026 weight=5;
}
server {
listen 9082;
proxy_connect_timeout 1s;
proxy_timeout 3s;
proxy_pass databoardServer;
}
}
4)七层反向代理。
server {
listen 443 ssl;
server_name 10.115.6.165;
ssl_certificate D:\\tmp\\opensslCrt\\demoAppChain.crt;
ssl_certificate_key D:\\tmp\\opensslCrt\\demoApp.key;
location / {
proxy_pass https://10.115.6.165:19026;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Referer https://10.115.6.165;
}
}
关于 Spring Session Cookie Secure 配置
当时想直接配置 Spring Web 应用不让 Cookie 带上 Secure 属性,这样就不用在 Nginx 上处理 Cookie 了。于是直接修改配置 application.properties 如下,但是返回给 nginx 的 Cookie 还是带 Secure 属性。
server.session.cookie.secure=true
通过跟踪 Spring 源码发现,只要应用是运行在 HTTPS 协议下的,那么就会让生成的 Cookie 是 Secure 的。