乐趣区

关于nginx:2020再谈跨域

跨域这个话题曾经谈了很多年了,怎么当初又要谈这个问题?原本是能够不用再提了的,然而因为 Chrome 86 版本当前又减少了很多限度,导致咱们不得不再次提起。

CORS

对于前端开发来说,跨域通常有两种形式,一种是在服务端批改 nginx 配置,在 response headers 里增加 CORS 设置,另一种是在本地架设代理。咱们先谈第一种。

本来在 nginx 里增加 CORS 曾经是一种惯例操作,简略到变本加厉:

    location /somewhere/ {if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin "$http_origin";
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
            add_header Access-Control-Allow-Headers "sitessubid,Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
            add_header Content-Length 0;
            add_header Content-Type text/plain;
            return 200;
        }
        if ($request_method = POST) {
            add_header Access-Control-Allow-Origin "$http_origin";
            add_header Access-Control-Allow-Credentials "true";
        }
    }

而后咱们只有在每个 axios 或者 fetch 申请里增加 withCredentials 就能主动把对应该服务器的 cookies 随申请一并发送了。

但问题在 Chrome 86 版本当前,Chrome强制给从服务端下发的每个 cookie 都减少了一个缺省的 SameSite 属性,并且强制把这个属性设置为 Lax,从而导致咱们的withCredentials 不起作用,只有是跨域的状况,cookies连取都取不到,更不要提发送了。在这里我不想再详述 SameSite 的原理,感兴趣的能够去这里理解。

这就须要咱们必须从服务端动手,批改 nginx 下发 cookies 时的 SameSite 属性。

SameSite 和 secure

批改 SameSite 又分为两种状况:如果你用的是低版本的 nginx,可能还不肯定能齐全解决问题,目前惟一能批改的变通之道是批改cookiepath,使它前面带上 SameSite 属性,例如这样:

proxy_cookie_path ~(.*) "$1; SameSite=none";

这里理论批改的是 cookie 里的 path 值,但因为附加了 ;,所以浏览器会认为自; 当前的是新属性,所以也会承受这个 SameSite 设定。

然而仅此还不够,Chrome又要求如果 SameSite 值为 none 的话,则还必须设置 secure 属性,也就是要求所有下发 cookie 的域名必须应用 https 协定,所以最终批改的后果是:

proxy_cookie_path ~(.*) "$1; SameSite=none; secure";

但这种状况只能解决相似于 cookie 是以 path 结尾的状况,如果上游服务器下发 cookie 时不止有 path,并且在path 结尾指定了其它的 SameSite,那就无能为力了,因为这么批改path 之后 cookie 会变成:

path=/; SameSite=none; secure; SameSite=Lax

浏览器依然以最初一个 SameSite=Lax 为准。目前低版本 nginx 对这个问题无解。

但如果你是 nginx 1.19.3 及以上,新增了一个属性,能够更好地解决这个问题:

proxy_cookie_flags one samesite=none;

增加 secure 标记的办法相似,参考官网文档,不赘述。

localhost 的 https

由上可知,咱们能够通过批改 nginx 的办法来革新跨域 cookies 的发送。但这时如果你又不想跨域了,还想用本地代理的形式来解决,又会遇到一个问题:因为咱们下面在下发 cookies 的时候,缺省地给每个 cookie 都带上了 secure 属性,这就导致咱们在应用本地代理的时候反而无奈设置 cookies,因为咱们本地的开发服务器往往应用的是相似于http://localhost:8080 这样的地址,它不是 https 协定,所以无奈设置 securecookies,那么怎么办呢?

咱们只能入手革新咱们的本地服务器,使它也反对 https 协定:

第一步,咱们学生成根证书:

openssl genrsa -des3 -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem

这时候咱们会失去两个文件,一个是rootCA.key,一个是rootCA.pem

咱们把 pem 文件导入零碎的钥匙链,并给予它齐全的信赖,这样当前再由它签发的证书能力被零碎认可,否则即便咱们用它签发了证书,浏览器一样会回绝。

第二步,生成 localhost 证书:

咱们先建设一个名为 server.csr.cnf 的文件:

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=US
ST=RandomState
L=RandomCity
O=RandomOrganization
OU=RandomOrganizationUnit
emailAddress=hello@example.com
CN = localhost

而后执行以下命令生成 server.key 文件

openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config <(cat server.csr.cnf)

再创立一个 v3.ext 文件:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost

而后执行以下命令生成 server.crt 文件:

openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext

好了,到此为止,这个 server.keyserver.crt文件就是咱们真正须要的两个文件,咱们把它们引入零碎,不同的零碎有不同的引入办法,以下例子是 react 的办法,建设一个 .env 文件:

SSL_CRT_FILE=server.crt
SSL_KEY_FILE=server.key
HTTPS=true

这时候再启动你的工程,localhost就带有 https 协定了。


跨域就是这么麻烦,但不论如何麻烦,咱们不辞麻烦,也肯定要解决它!

退出移动版