nginx单端口支持http和https多协议

前情概要:企业系统一开始上线只支持http,后面数据上来了,数据中带有url地址的用的也是http。现在为了安全考虑要升级https,我们知道nginx中一个端口只有一种协议,甲方又只给了一个对外端口,那么历史数据中带有http的地址怎么办?势必会遇到497错误。其实这个问题还算容易解决,直接据库替换也能解决,但是生成的二维码要如何解决?有的还把二维码打印出来了。。。

所以我们要有一个方法,可以在同一个端口既能支持http又能支持https,并且http要强制跳转到https,满足安全需求。nginx的ngx_stream_ssl_preread_module模块就是用来解决这个问题的:

The ngx_stream_ssl_preread_module module (1.11.5) allows extracting information from the ClientHello message without terminating SSL/TLS, for example, the server name requested through SNI or protocols advertised in ALPN. This module is not built by default, it should be enabled with the –with-stream_ssl_preread_module configuration parameter.

简单一句就是这个模块能够请求时判断请求用的是不是 ssl 协议,以及ssl的协议版本。

一、安装模块

在最新版的nginx中,这个模块默认安装了。我遇到的是用 yum 安装nginx没有 stream 模块。

# yum install nginx-mod-stream -y                                                                                

nginx模块安装路径在:/etc/nginx/nginx.conf。然后在nginx.conf配置文件里加载模块:

load_module /usr/lib64/nginx/modules/ngx_stream_module.so;

但是在新版的nginx中会提示你不需要手动 load_module,因为nginx已经自动加载好了。

二、主配置

在 nginx.conf 配置中加:

stream {
upstream http_gateway {
server 127.0.0.1:4000;
}
upstream https_gateway {
server 127.0.0.1:4001;
}
# 根据不同的协议走不同的upstream
map $ssl_preread_protocol $upstream{
// 写法一:
default http_gateway;
"TLSv1.0" https_gateway;
"TLSv1.1" https_gateway;
"TLSv1.2" https_gateway;
"TLSv1.3" https_gateway;
// 写法二:
// default https_gateway;
// "" http_gateway;
}
server {
listen 8080;
ssl_preread on;
proxy_pass $upstream;
}
}

然后在站点配置文件中:

server {
listen 4001 ssl;
server_name 域名;
ssl_certificate cert/ssl.crt;
ssl_certificate_key cert/ssl.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;

...
}

server {
listen 4000;
server_name 域名;
return 301 https://$host:8080$request_uri;
}

上面的含义:访问服务器8080端口,会走到 $ssl_preread_protocol 中,根据映射关系,如果请求头带 ssl 协议,走 https 的server,不然走 http 的server。

跟反向代理不一样,反向代理对外表现的只是 8080 server配置的内容;ssl_preread则是把上有的server给拿了过来。也就是说如果是用http协议访问8080,则8080的server相当于:

server {
listen 8080;
server_name 域名;
return 301 https://$host:8081$request_uri;
}

如果https协议访问8080,则其server变成了:

server {
listen 8080;
server_name 域名;
ssl_certificate cert/ssl.crt;
ssl_certificate_key cert/ssl.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
}

是不是非常nice😊?这不是相当于一个端口配置了两种协议了么😏?上面在4000 server中配置了http到https的重定向,也就是说无论http还是https协议,最后都走到了https,完美实现需求,历史数据都不是问题了。