缘起
笔者目前负责的一个可视化自助搭建平台,最开始是使用http部署的。随着迭代不断进行,特别是逐渐开始涉及到支付、涉密数据等需求,我们的站点就开始支持https。这时候的情况是,对于同一个网址,我们可以使用http和https协议,访问同一内容。
然而还需要解决几个问题才能完美解决问题。
如何配置证书
当你得到ssl证书之后,需要在你的nginx配置文件中打开ssl,同时指定好端口(默认是443)、证书文件、加密密钥文件等等。
最简单的配置,如下:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.chained.crt;
ssl_certificate_key www.example.com.key;
}
在需要更高安全性时候,还可以配置ssl_dhparam参数 如:
ssl_dhparam /etc/nginx/certs/dhparam.pem;
当你配置了这些,如果需要测试环境也能支持https,那么我们需要对dev-server也做一些设置。
以Vue项目为例:
如果是webpack启动的项目,可以如此配置vue.config.js或webpack.config.js:
const fs = require('fs')
......
devServer: {
host: "www.example.com',
port: 443,
https: {
key: fs.readFileSync('私钥文件路径'),
cert: fs.readFileSync('证书文件路径')
}
}
......
如果使用Vite,可以如此配置vite.config.js:
server: {
port: 443,
host: 'www.example.com',
https: {
key: fs.readFileSync('私钥文件路径'),
cert: fs.readFileSync('证书文件路径')
}
}
这样就能在测试环境使用https了。
跨协议资源访问
自2019年12月发布的Chrome 79版本后,对于在https协议下的http资源访问,会被阻止掉,并报错。
其实解决这个问题有一个比较简单的做法,就是去掉协议头。如资源:https://p3.ssl.qhimg.com/t0135a8fa9db2be41bc.png ,写成://p3.ssl.qhimg.com/t0135a8fa9db2be41bc.png 。从浏览器的角度讲,如果包含资源的网址是http,则自动访问http地址;如果是https,则访问https地址。
这样做的前提是,资源所在的服务器支持http/https两种方式的访问。
将内容去掉协议头的方案,一种是直接在资源引用处进行替换。这种比较简单和明晰,也是我们现在使用的方案。
另一种比较彻底的,是使用nginx或openresty+lua对http内容进行输出前的替换。你可以参考这里 将http和https协议头替换为空即可。
这样做,所有经过nginx/openresty的请求,最终的协议头都会被干掉,比较彻底。
我们之所以没有采用这种做法原因有二:第一个是这种方案存在一定误杀的概率,即特定需要保留协议头的也会被干掉;第二个,运行时的替换终归会有一定的性能损耗,这样的方案对于请求结构不算复杂的系统,得不偿失。
目前我们对系统进行一次性替换,工作量尚可。因此我们采用第一种方案。
放入iframe中的联合登录
我们的系统需要完整嵌入内部一个IM协作项目,这个项目采用Web技术+Electron编写。在Web端,使用的是https的url地址,同时,与我们的项目url地址域名不一致。
在Chrome80以后的版本,请求iframe内的url时,默认不带非同域cookie,这可能会造成使用cookie验证登录的服务等丢失登录态。
为此我们有两种方案:
方案一:服务端在种cookie时,将secure设置为true,samesite设置为None,并且使用https协议。
方案二:应用验证登录时候,一律使用token方式验证。这样要求前端在每个请求中加入token以及失效重取方案。
由于方案二对现有系统改动较大,故而采用第一种方案。
但是采用第一种方案,http站点登录就不可用了。考虑到我们http站点的作用仅在于展示发布后的大屏,保证其能访问http资源即可。
那么,我们只要考虑将http站点统一跳转到https即可,但是,并不是一跳了之的。
排除某些路径的跳转
其实最简单的跳转,在nginx配置文件中,一句话就能解决,我们在80端口的配置文件下加入:
rewrite ^(.*)$ https://$host$1 permanent;
就可以永久地将http站点地址跳转到同路径的https地址。
别忘了我们还有个隐含的需求:保持之前发布出去的大屏地址显示正常。如果已经发布出去的大屏采用了http资源,且这些站点不支持https访问,在浏览器中就会加载失败。因此,我们需要保持已发布大屏的http地址不变。
我们解决的思路是,判断如果是发布出去的大屏地址,则不跳转,否则触发跳转。
然而,nginx的配置文件只有if指令,并没有else指令,所以我们需要if指令,配合set、rewrite指令合作完成这个过滤。
set $flag 0;
if ($request_uri ~ "/public") {
set $flag 1;
}
if ($flag = 0) {
rewrite ^(.*)$ https://$host$1 permanent;
}
nginx反向代理后的IP问题
这个不是http转https产生的问题,是使用反向代理代理引入的。这里也一并说一下。
由于我们是前后端分离的系统结构,前端访问接口时,是通过反向代理代理到服务端的。
location /api/ {
proxy_pass http://example.com;
}
此时,如果不做其他设置,服务端得到的IP,将是nginx所在服务器的IP,而非访问者浏览器IP。
因此,当 nginx 作为反向代理时,需要将原始客户端 IP 地址存储在 HTTP 头字段 X-Real-IP 或 X-Forwarded-For 中并将其传递到后端服务器。
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这里,X-Real-IP是访问当前服务器的客户端IP。当经过多次代理,这个IP会变成上一次代理的IP。
X-Forwarded-For的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。
如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:
X-Forwarded-For: IP0, IP1, IP2
Comments