背景
网上搜到的很多Nginx CORS配置虽然也起作用,但是可能因未参考规范而缺少基本的逻辑判断,不能表达CORS的工作原理。
Tomcat中有参考自CORS specification的CORS实现CorsFilter,用起来非常方便,故我想把Tomcat CorsFilter的逻辑照搬到Nginx中。
然而不巧的是,Nginx的if指令不支持逻辑与而导致多条件判断非常吃力,加之If is Evil… when used in location context,还有add_header指令不继承上层add_header也让人头疼。最终选用headers-more模块more_set_headers来设置响应头,虽然不支持追加响应头,但因只需追加Vary响应头,故最中通过加一个map指令块解决。
以Debian/Ubuntu系统为例,使用步骤如下:
前提准备
安装Nginx,并安装Nginx模块headers-more
apt-get install nginx-full
apt-get install libnginx-mod-http-headers-more-filter
或自行编译的Nginx,configure时添加模块headers-more
配置
/etc/nginx/conf.d/cors.conf
map $http_origin $allow_origin {
# 允许私有网段IP
~^http://(192\.168|172\.(1[6-9]|2[0-9]|3[0-1])|10\.\d+)\.\d+\.\d+(:\d+)?$ $http_origin;
# 允许本机
~^http://(127\.0\.0\.1|localhost)(:\d+)?$ $http_origin;
# 允许*.example.com
~^https?://[a-z0-9-]+\.example\.com$ $http_origin;
default '';
}
map $sent_http_vary $comma_vary {
default "";
~.+ ",$sent_http_vary";
}
/etc/nginx/cors_params
# 需要在nginx.conf中 `include conf.d/*.conf` 或 `include conf.d/cors.conf`;
# preflight request
if ($http_access_control_request_method != "") {
more_set_headers "access-control-allow-origin: $allow_origin";
more_set_headers "access-control-allow-methods: $http_access_control_request_method";
# necessary only if 'http-access-control-request-headers' present
more_set_headers "access-control-allow-headers: $http_access_control_request_headers";
# necessary only if 'access-control-request-credentials' present
# more_set_headers "access-control-allow-credentials: true";
# necessary only if 'access-control-request-private-network' present
# more_set_headers "access-control-allow-private-network: true";
# can be up to 5 if 'access-control-request-private-network', up to 600 otherwise
more_set_headers "access-control-max-age: 600";
more_set_headers "vary: origin$comma_vary";
return 204;
}
# actual request
if ($http_origin != "") {
more_set_headers "access-control-allow-origin: $allow_origin";
more_set_headers "access-control-expose-headers: accept-ranges,content-range,last-modified,etag";
# to support Resource Timing API
# more_set_headers "timing-allow-origin: $allow_origin";
more_set_headers "vary: origin$comma_vary";
}
需要注意的是:不久的将来各浏览器将默认启用PNA preflight策略,到时对于私有网络(例如192.168.*.*, 172.16~31.*.*, 10.*.*.*
)以及本机127.*.*.*
的源,浏览器在发送的preflight请求时会携带请求头access-control-request-private-network: true
,而服务端需要添加响应头access-control-allow-private-network: true
以改进支持。
调用
/etc/nginx/sites-enabled/example.com.conf
server {
# ...
location / {
root /var/www/example.com;
include cors_params; # 放在location块尾端
}
location /api/ {
proxy_pass http://localhost:8080/api/;
include proxy_params;
include cors_params; # 放在location块尾端
}
}