Nginx以HTTP反向代理HTTPS的Exchange邮件服务

笔者使用Nginx反向代理时,上游服务强制启用了HTTPS访问,但我们的需求是以HTTP统一对外提供服务。

经过一些探索,发现问题主要来源于上游应用302跳转、set-cookie响应头的secure属性两方面,需要合理调整Nginx的站点配置文件来解决。

This post shows how to proxy HTTPS Exchange Mail service with HTTP protocol when using Nginx reverse proxy. The key point is to handle 302 redirect and secure attribute in configuration of Nginx.

文章参考了 浅流 - Nginx以HTTP反向代理HTTPS服务 这篇文章,但其对Nginx的more_set_headers属性设置有问题,导致set-cookie头从secure属性的后面截断,在具有多个set-cookie响应头的登录场景中不适用。


问题发现

问题背景参考原文,用以下配置运行 Ngnix, 使其用 HTTP 协议在 9080 端口反向代理 19026 上的 HTTPS 服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 ,就会发现如下图所示的两问题:

image.png

后端服务使用 redirect 重定向导致的问题

浏览器地址栏上显示被重定向到了https://10.115.6.165/databoard/dataCmder .

这是因为后端Web应用执行了redirect重定向语句,而重定向的协议、地址是基于web应用上下文的,而nginx并没有做特别的处理就转发给了浏览器,浏览器自然不能访问到这个地址。解决办法如下:

1
2
3
4
5
6
7
8
9
10
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';

Cookie的Secure属性,意味着保持Cookie通信只限于加密传输,指示浏览器仅仅在通过安全/加密连接才能使用该Cookie,而我们的需求是以HTTP方式传送。如果不去掉,浏览器会提示不接受这个Cookie。

image-1.png

对于该问题,原文采用的方案是通过nginx的more_set_headers模块,通过map中正则表达式对Set-Cookie进行改写。

但该方案会导致Set-Cookie直接从secure属性的前面截断,如果secure属性在中间,或者是有多个Set-Cookie属性时,就无法适用。

经过查阅 Nginx官方文档中的 Module ngx_http_proxy_module ,发现从nginx 1.19.3开始,加入了proxy_cookie_flags的directive,恰好可以去掉secure属性并且加入samesite属性(如果不加入samesite属性,浏览器一样会拒绝)。

image-2.png

因此,完整配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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'; # 取消原文中的替换
proxy_cookie_flags ~ nosecure samesite=strict; # 调整为官方的方法
}
}

成功去掉了secure属性,加上了samesite属性。

image-5.png

但是,需要注意的more_set_headers仅在Nginx 1.19.3以上才支持,因此您需要升级Nginx版本,以使用该方案。


Ubuntu编译安装新版本Nginx并加入相关模块支持

如果不清楚如何升级自己的Nginx,可以参考以下。

执行 apt search nginx 会发现Ubuntu22.04的apt源中nginx版本太老,为1.18.0,不能满足我们的需求。

image-3.png

因此需要从源码编译安装,需要注意的是添加OpenSSL模块(不然无法代理HTTPS的服务)、并且添加 headers-more-nginx-module 这个附加模块。 (https://github.com/openresty/headers-more-nginx-module)

使用以下命令进行编译配置。

image-4.png

完成后 make && sudo make install 即可。

安装完毕之后, 默认路径在 /usr/local/nginx/sbin/nginx,因此可以 sudo ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx 建立一个软链接。

编译安装的nginx默认没有sites-enabled这个目录,可以手工在其conf目录新建一个,并且在nginx.conf中引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# nginx.conf

events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

...
...

include /usr/local/nginx/conf/sites-enabled/*;

}

随后将其注册为 systemd 服务,在 /etc/systemd/system 新建一个 nginx.service :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# /etc/systemd/system/nginx.service

[Unit]
Description=nginx - high performance web server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s stop
PrivateTmp=true

[Install]
WantedBy=multi-user.target

记得 sudo systemctl daemon-reload 重载服务,再 sudo systemctl start nginx 即可运行。

Nginx以HTTP反向代理HTTPS的Exchange邮件服务

https://www.catop.top/2024/01/17/nginx-proxy-exchange-mail-with-http/

作者

Catop

发布于

2024-01-17

更新于

2024-11-10

许可协议

评论