我尝试为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;
}
}
}
但是,此配置必须使用两个条件:一个条件匹配源域名,另一个条件捕获飞行前请求。因此,当第二个条件匹配时,第一个条件中的标头不会添加到响应中。
根据《如果是邪恶》的官方文章,这是nginx的预期行为。
如果If Is Evil
,我该如何在nginx中启用CORS?或者,也许有一种方法可以以某种方式克服这种限制?
您可以尝试使用第一个if
块的map
istead:
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
标头并且与此regex匹配时,才会添加这些标头。
一个更兼容的解决方案涉及的内容更多,但它确实会为域匹配消除正则表达式的重复,并且可以放入片段中。
我在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-zd]+.)+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';
}
然后在多个location { ... }
块内可以是include
d的/etc/nginx/snippets/cors-site.conf
:
# 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;
要匹配的值中的#
并不特殊,它们只是用作分隔符,允许使用多个输入变量进行测试。对于$cors_site_origin
,可以在map
中添加额外的域,但需要进行一些调整,以支持具有不同允许选项/标头的域。
到目前为止,我找到的唯一解决方案是使用一个变量聚合多个条件,然后只将其与一个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;
}
}
}
如果不深入了解nginx设置的细节,它无论如何都不会工作,因为您返回的CORS头是不正确的。。。
具体而言:
-
对于飞行前(OPTIONS)请求,以下是唯一有意义的CORS响应标头:
Access-Control-Allow Origin
、(必需)、Access-Control-Allow Credentials
(可选)、Access-Control-Allow-Methods
、(必要)Access-Control-Allow-Headers
、1(必需)和Access-Control-Max-Age
(可选)。任何其他都将被忽略。 -
对于常规(非OPTIONS)请求,以下是唯一有意义的CORS响应标头:
Access-Control-Allow Origin
(必需)、Access-Control-Allow Credentials
(可选)和Access-Control-Expose-Headers
(可选)。任何其他都将被忽略。
注意那些飞行前请求所需的标题-目前您只传递了其中两个。。。此外,请注意,对于非OPTIONS请求,不需要返回Access-Control-Allow-Methods
-它不是"有效"的,因此将被忽略。
就你的具体nginx问题而言,我认为@Slava Fomin II有正确的答案。。。