Nginx配置,如何支持 CORS?多来源域名安全配置
发布于 作者:苏南大叔 来源:程序如此灵动~
关于跨站脚本攻击,对于目前的情形来说,是越来越难以成立了。目前的浏览器环境下,对于越权调用脚本的行为,必须要求:被攻击的网站,错误的设置了cors,才有可能调用成功了。本文以nginx配置的角度,看看如何配置cors。什么样的情况下,配置才可能被恶意利用呢?

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:win10,chrome@135.0.7049.41,php@8.2.9nts,nginx@1.15.11。CORS(跨域资源共享)是现代浏览器的一项安全功能,用于限制从一个域访问另一个域的资源,配置Nginx支持CORS是一个常见的需求。
CORS
CORS的实质就是:服务器端返回几个特殊的header,并且在接收到options请求的时候,返回特定的header及状态。对应的header有很多变化,允许服务器声明哪些来源可以访问其资源。
核心内容就是:允许什么样的网站给自己发请求,可以发送什么样的请求(由客户端所在的浏览器控制发送)。
通过设置HTTP响应头,服务器可以控制跨域请求的行为。常见的CORS相关头包括:
Access-Control-Allow-Origin:指定允许访问的来源域名(例如:https://newsn.net)。Access-Control-Allow-Methods:指定允许的HTTP方法(如GET、POST、OPTIONS)。Access-Control-Allow-Headers:指定允许的自定义请求头。Access-Control-Allow-Credentials:是否允许发送Cookie。
配置 CORS
在Nginx中,可以通过添加响应头来支持CORS。值得说明的是:这些特殊的header,不一定必须由nginx发送,服务端脚本发送也是可以的。只不过,一般的情况下,nginx布置在最前端,所以由它进行配置比较方便。
并且需要明确的观点是:
nginx默认用来配置普通静态文件的,而静态文件是不需要设置cors的,而且静态文件自身也无法定制header。- 需要设置
cors的,都是后端功能。而后端功能自身就可以添加header。当然也可以通过“反代”挂载在nginx下,由nginx统一配置header。 nginx反代功能可以按请求特征单独配置,也可以放在某个location /{}下面。cors的设置需要和反代的部分放置到一起,才可以生效。
参考文章:
- https://newsn.net/say/nginx-thinkjs.html
- https://newsn.net/say/nginx-conf-proxy.html
- https://newsn.net/say/nginx-sub_filter.html
配置示例
以下是一个基本的配置示例。(注意:nginx配置文件的变化很多,这并不是唯一正确答案)
server {
listen 80;
server_name ttt;
location / {
root /usr/share/nginx/html;
index index.php index.html index.htm;
}
location ~ \.php$ {
# root /var/www/html;
# autoindex off;
fastcgi_pass lamp-php-container:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
include fastcgi_params;
# 动态跨域处理
set $cors_origin '';
if ($http_origin ~* (http://h|http://newsn.net|https://newsn.net)) {
set $cors_origin $http_origin;
}
# 添加跨域响应头
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With' always;
# 处理 OPTIONS 请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With' always;
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Length' 0;
return 204;
}
}
}
配置的框架模版是这样的:
server {
listen 80;
server_name newsn.net;
location / {
# 静态资源类设置
location ~* \.(html|css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|otf|eot|ttf|txt|json)$ {
try_files $uri $uri/ =404;
}
# cors设置
# 反向代理
}
}或者(以php为例):
server {
listen 80;
server_name newsn.net;
location / {
# ..
}
location ~ \.php(.*)$ {
# cors设置
# 反向代理
}
}CORS 设置
下面的代码里面,可以看到options的作用范围内和范围外,有相同的代码,不要试图省略掉其中一个。nginx的配置和大家的想像不一样。
# 添加 CORS 相关头
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
# 处理预检请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
return 204;
}豪华版设置:
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'http://h';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With';
add_header 'Content-Type' 'text/plain charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'http://h';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,access-control-allow-origin, authority, content-type, version-info, X-Requested-With';反向代理
cors的配置,需要和反向代理的配置,放在一起。否则就很有可能会让人怀疑人生了。为啥没生效?
最常见的情况
反代非php代码的时候,一般就是修改backend_service的地址(或者说端口号)。
location / {
#...
# 反向代理到后端服务
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}nodejs,一般启动端口3000。java,一般启动端口8080。Python(Flask或Django),一般启动端口5000。
php反代
php(PHP-FPM)的情况,相对特殊。可以使用以下配置:
location / {
#...
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}或者:
location ~ \.php(.*)$ {
fastcgi_pass 127.0.0.1:9001;
fastcgi_index index.php;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
include fastcgi_params;
}参考文章:
技术细节:通配符 星
运维或程序员,为了图省事,一般会设置Access-Control-Allow-Origin为*。这并不是推荐的做法,因为这样就给一些网站非法调用接口留下来口子。
add_header 'Access-Control-Allow-Origin' '*';正确的做法是,确定合法的调用网站,然后配置:
add_header 'Access-Control-Allow-Origin' 'http://h';或者设置多个允许调用的网站时,是做了一个动态筛选。
set $cors_origin '';
if ($http_origin ~* (http://h|http://newsn.net|https://newsn.net)) {
set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;技术细节:always
在 Nginx 配置中,always 是一个指令修饰符,用于确保特定的响应头始终被添加到响应中,无论响应的状态码是什么。
作用:
默认情况下,add_header 指令只会在响应状态码为 2xx(成功)或 3xx(重定向)时生效。
如果你希望无论状态码(如 4xx 或 5xx)如何,都强制添加响应头,就需要使用 always 修饰符。
所以,在本文的语境里面,加不加always,都没有什么问题。
题外话:php设置headers
如果是服务器端代码设置头信息,这里有个php的例子:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: https://h'); // 允许的来源
header('Access-Control-Allow-Credentials: true'); // 允许携带Cookie
header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); // 允许的HTTP方法
header('Access-Control-Allow-Headers: Content-Type'); // 允许的自定义头部
exit; // 结束 OPTIONS 请求
}结语
在 Nginx 中配置 CORS,是跨域接口对接的必备设置项目,请注意把Access-Control-Allow-Origin设置为*的时候,可能会引发的安全问题。更多nginx配置文章,请点击苏南大叔博客: