lanlin/notes

CORS setting not working when allow credentials or 当 credentials true 时跨域配置无效的问题

lanlin opened this issue · 1 comments

情景

通过浏览器 XHR 发起跨域请求,而且需要携带 cookie 或者 Authentication 信息时。
如果发现一旦设置 Access-Control-Allow-Credentials: true 就无法实现正常跨域时,可以参考本文。

解决方法及原因分析 (以 Nginx 为例)

1. 浏览器的原因

一般你在网上找到的设置 nginx 跨域的文章,很多都是类似如下的 wildcard 配置

add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' '*';
add_header 'Access-Control-Allow-Headers' '*';

简单来说就是为了图省事,用星号来表示允许来自所有源、方法以及头信息的跨域请求。
这样的设置,在不需要携带 cookie 和 authentication 的信息时,除了不安全以外,倒是能正常实现跨域。

但是,现在浏览器对于跨域的限制越来越严格。如果配置中包含 add_header 'Access-Control-Allow-Credentials' 'true'; 则上面三项配置必须设置其具体的值,如果任意一项包含星号,则跨域请求仍然会失败。

2. Nginx 设置原因

如果你的 Nginx 是对后端程序进行反向代理,比如反向代理 PHP。
此时,由于后端程序返回的数据中包含有头信息。Nginx 一般会直接将该头信息返回给浏览器,而不会将你配置在 Nginx 中的跨域信息添加到其中。

add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' '*' always;
add_header 'Access-Control-Allow-Headers' '*' always;

这个时候你需要像上面那样,在 add_header 的末尾加上 always 来强制 Nginx 在返回数据时携带上跨域头信息。

3. 程序的原因

理论上,你在 Nginx 中配置的跨域头信息,只有在上游程序返回“正常”的头信息状态码时,才会被携带返回给浏览器。

一般来说,设置 always 应该对这种情况也起作用。
如果仍然无效,那么建议你直接把 Nginx 的跨域设置全部删除,直接在上游程序中返回相应的头信息,完全不用理会 Nginx 配置。

其他补充

1. 浏览器如何处理跨域请求

浏览器在处理 XHR 跨域请求时,会分两步来进行。

第一步,发送一个 OPTIONS 的预检请求来询问服务器对于跨域的授权策略。

此时,头信息会携带 access-control-request-headers 以及 access-control-request-method 这两项。
分别表示下一步将会被携带的头信息项,以及将采用的提交方法。而 Nginx 返回中的 Access-Control-Allow-Headers 项必须包含对应的项,Access-Control-Allow-Methods 中也必须包含对应的方法,表示允许第二步以这中方式提交。

如果返回的授权策略不符合以上要求,则第二步的请求会被浏览器自动取消。

第二步,浏览器正式发出请求。

此时,Nginx 返回的头信息中仍然必须包含 Allow Origin、Allow Headers 以及 Allow Methods 这三项。
不然,浏览器在收到返回数据后,会自动将该请求标记为失败,而不会将返回数据传递给 JS。

需要注意的是,OPTIONS 请求可能不会每次都出现,原因在于浏览器是否对其进行了缓存。
而且,理论上第二步除 Allow Origin 以外, Allow Headers 和 Allow Methods 的值设置为空字符串也不影响。

2. Nginx Plus

Nginx 是有个 Plus 版的。Plus 版是收费的,但是有很多动态模块可以使用,提供了一些更强大的功能。
比如,普通版的 Nginx 是没有提供一个变量来获取所有请求头的,但是在 Plus 中却可以通过安装其他模块来实现这一点。

ngx_http_lua_module 模块为例。
在 Nginx Plus 中,安装了该模块后,就能用如下代码来获取请求中的头信息。

set_by_lua $request_headers '
        local t = {}
        local h = ngx.req.get_headers()
        for k, _ in pairs(h) do
            t[#t+1] = k
        end
        return table.concat(t, ",")
    ';

3. 另类的奇葩

有一个叫 Openresty 的玩意,通过扩展 Nginx, 把 LuaJIT 跟 Nginx 绑定到一起。
搞出来一个 框架+WEB服务器 的奇葩产品,或者叫框架内置服务器?我也不懂,反正据说罗锤子给他们捐过。
敢信去的朋友可以去尝试一下。