09月19, 2023

网站http自动跳转https的一些琐记

缘起

笔者目前负责的一个可视化自助搭建平台,最开始是使用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资源访问,会被阻止掉,并报错。

image.png

其实解决这个问题有一个比较简单的做法,就是去掉协议头。如资源: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

参考资料

  1. https://www.runoob.com/w3cnote/http-x-forwarded-for.html
  2. https://new-developer.aliyun.com/article/1182253
  3. https://www.zhihu.com/question/587347926/answer/2974750154
  4. https://blog.csdn.net/jiao_fuyou/article/details/36010691

本文链接:https://www.leon82.com/post/http-to-https.html

-- EOF --

Comments