nginx 配置以启用具有源匹配的 CORS
Posted
技术标签:
【中文标题】nginx 配置以启用具有源匹配的 CORS【英文标题】:nginx config to enable CORS with origin matching 【发布时间】:2019-06-16 05:10:09 【问题描述】:我尝试将a very popular config 用于 nginx,它启用了 CORS 并支持使用正则表达式进行源匹配。
这是我的配置:
server
listen 80 default_server;
root /var/www;
location /
if ($http_origin ~ '^http://(www\.)?example.com$')
add_header Access-Control-Allow-Origin "$http_origin";
# Handling preflight requests
if ($request_method = OPTIONS)
add_header Content-Type text/plain;
add_header Content-Length 0;
return 204;
但是,此配置必须使用两个条件:一个与源域名匹配,另一个用于捕获预检请求。因此,当第二个条件匹配时,第一个条件的标头不会添加到响应中。
根据If Is Evil官方文章,这是nginx的预期行为。
如果If Is Evil
那么如何在 nginx 中启用 CORS?或者也许有办法以某种方式克服这个限制?
【问题讨论】:
相关:***.com/questions/29467671/… 和 ***.com/questions/31017524/… @sideshowbarker 提到的帖子在这里不仅相关而且非常重要,因为只有最后一个if
中的 add_header
s 在这种情况下有效
【参考方案1】:
你可以尝试使用map
而不是第一个if
块:
map $http_origin $allow_origin
~^http://(www\.)?example.com$ $http_origin;
map $http_origin $allow_methods
~^http://(www\.)?example.com$ "OPTIONS, HEAD, GET";
server
listen 80 default_server;
root /var/www;
location /
add_header Access-Control-Allow-Origin $allow_origin;
add_header Access-Control-Allow-Methods $allow_methods;
# Handling preflight requests
if ($request_method = OPTIONS)
add_header Content-Type text/plain;
add_header Content-Length 0;
return 204;
nginx 将拒绝添加空的 HTTP 标头,因此只有在请求中存在 Origin
标头并且匹配此正则表达式时才会添加它们。
【讨论】:
一个有趣的想法,谢谢!有没有办法对正则表达式进行重复数据删除? 我看到的唯一方法是使用带有第三个map
块的附加变量,例如map $http_origin $use_cors_headers ...
,然后在另外两个中检查这个变量值,但我不确定是否这将是任何性能改进。
只有add_header
s 内的if
对我和其他一些开发人员在这种情况下有效(参见***.com/questions/29467671/…)【参考方案2】:
到目前为止,我发现的only solution 是一种使用变量来聚合多个条件,然后仅将其与单个 if 语句匹配的技巧,因此重复了一些指令:
server
listen 80 default_server;
root /var/www;
location /
set $cors '';
set $cors_allowed_methods 'OPTIONS, HEAD, GET';
if ($http_origin ~ '^https?://(www\.)?example.com$')
set $cors 'origin_matched';
# Preflight requests
if ($request_method = OPTIONS)
set $cors '$cors & preflight';
if ($cors = 'origin_matched')
add_header Access-Control-Allow-Origin $http_origin;
if ($cors = 'origin_matched & preflight')
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods $cors_allowed_methods;
add_header Content-Type text/plain;
add_header Content-Length 0;
return 204;
【讨论】:
【参考方案3】:如果不深入了解您的 nginx 设置的详细信息,它无论如何都不会工作,因为您返回的 CORS 标头不正确...
具体来说:
对于预检 (OPTIONS) 请求,以下是唯一有意义的 CORS 响应标头:Access-Control-Allow Origin
、(必需)、Access-Control-Allow Credentials
(可选)、Access-Control-Allow-Methods
、(必需)、Access-Control-Allow-Headers
、(必需) 和 Access-Control-Max-Age
,(可选)。任何其他都将被忽略。
对于常规(非 OPTIONS)请求,以下是唯一有意义的 CORS 响应标头:Access-Control-Allow Origin
(必需)、Access-Control-Allow Credentials
(可选)和 Access-Control-Expose-Headers
(可选的)。任何其他都将被忽略。
注意那些 required 飞行前请求的标头 - 目前您只传递其中两个...另外,请注意您不需要返回Access-Control-Allow-Methods
用于非 OPTIONS 请求 - 它不是“有效的”,因此将被忽略。
就您的具体 nginx 问题而言,我认为 @Slava Fomin II 有最正确的答案...
【讨论】:
感谢您提及Access-Control-Allow-Methods
仅用于预检请求。我错过了。但是,如果我根本不关心请求标头,我应该在 Access-Control-Allow-Headers
中返回什么值?我主要提供静态内容。实际上,我是否需要为这种场景实现预检请求处理,或者简单的 CORS 请求对所有浏览器都足够了?
好吧,提取规范(定义 CORS,位于 fetch.spec.whatwg.org/#http-cors-protocol)几年前已更改为允许 '*' 用于附加标头 - 特别是“注意: 对于Access-Control-Expose-Headers
、Access-Control-Allow-Methods
和Access-Control-Allow-Headers
响应标头,值*
算作没有凭据的请求的通配符。对于此类请求,无法单独匹配标头名称或方法*
。”但目前尚不清楚浏览器是否真的支持这一点。
因此,如果不支持Access-Control-allow-Headers: *
(而且似乎没有人知道它是否真的支持),那么最好的办法是提取Access-Control-Request-Headers
请求标头和“镜像”的值在Access-Control-Allow-Headers
响应标头中返回。
这是有道理的,但是如果我想告诉浏览器——“给我发送尽可能少的标题”怎么办?那我应该在Access-Control-Allow-Headers
中输入什么?如果Access-Control-Allow-Headers
headers 完全丢失,浏览器将如何处理响应?
嗯,Access-Control-Request-Headers
(ACRH) 请求标头是浏览器告诉服务器“我即将发出请求,这些是我要发送的标头作为要求”。服务器无法控制将在请求中发送哪些标头...您的Access-Control-Allow-Headers
(ACAH) 响应标头需要(至少)包含任何不是“简单”标头的标头。如果您不从服务器发送 ACAH 响应标头,则请求将失败。【参考方案4】:
更合规的解决方案涉及更多,但确实会删除重复的正则表达式以进行域匹配,并且可以放入 sn-ps。
我在http ...
块内创建了文件/etc/nginx/snippets/cors-maps.conf
,它必须是include
d。它包含如下规则:
# always set value to append to Vary if Origin is set
map $http_origin $cors_site_v
~. 'Origin';
# set site-specific origin header if it matches our domain
map $http_origin $cors_site_origin
'~^https://(?:[-a-z\d]+\.)+example\.com$' $http_origin;
# validate the options only if domain matched
map '$request_method#$cors_site_origin#$http_access_control_request_method' $cors_site_options
# is an allowed method
'~^OPTIONS#.+#(?:GET|HEAD|POST|OPTIONS)$' okay;
# requested an unknown/disallowed method
'~^OPTIONS#.' nope;
# set value of Access-Control-Allow-Origin only if domain matched
map '$request_method#$cors_site_origin' $cors_site_acao
'~^(?:GET|HEAD|POST)#.' $cors_site_origin;
# set value of Access-Control-Allow-Credentials only if Origin was allowed
map $cors_site_acao $cors_site_acac
~. 'true';
然后/etc/nginx/snippets/cors-site.conf
可以是多个location ...
块内的include
d:
# only using "if" safely with a "return" as explained in https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/
# return early without access headers for invalid pre-flight, because origin matched domain
if ($cors_site_options = nope)
add_header Vary $cors_site_v;
return 204 '';
# return early with access headers for valid pre-flight
if ($cors_site_options = okay)
add_header Access-Control-Allow-Origin $cors_site_origin;
add_header Access-Control-Allow-Credentials $cors_site_acac;
add_header Vary $cors_site_v;
add_header Access-Control-Allow-Methods 'GET, HEAD, POST, OPTIONS';
# probably overkill, gleaned from others' examples
add_header Access-Control-Allow-Headers 'Accept, Accept-Language, Authorization, Cache-Control, Content-Language, Content-Type, Cookie, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Mx-ReqToken, X-Requested-With';
add_header Access-Control-Max-Age 1728000;
return 204 '';
# conditionally set headers on actual requests, without "if", because directive ignored when values are empty strings ("map" default)
add_header Access-Control-Allow-Origin $cors_site_acao;
add_header Access-Control-Allow-Credentials $cors_site_acac;
add_header Vary $cors_site_v;
要匹配的值中的#
并不特殊,它们只是用作分隔符以允许使用多个输入变量进行测试。可以将额外的域添加到 map
的 $cors_site_origin
,但需要进行一些调整以支持具有不同允许选项/标题的域。
【讨论】:
以上是关于nginx 配置以启用具有源匹配的 CORS的主要内容,如果未能解决你的问题,请参考以下文章
csharp 以下代码为具有指定源的整个应用程序启用CORS:
(Laravel&Nginx)CORS标题'Access-Control-Allow-Origin'与'(null)'不匹配