Nginx配置CORS实现资源跨域

同源策略是Web应用程序安全模型中的一个重要概念。根据该策略,Web浏览器允许第一个Web页面中包含的脚本访问第二个Web页面中的数据,但前提是两个Web页面具有相同的源。同源定义为协议,主机名和端口号相同的组合。此策略可防止一个页面上的恶意脚本通过该页面的文档对象模型访问另一个网页上的敏感数据。

AJAX规避同源策略三种方式

JSONP:请求阶段:浏览器创建一个 script 标签,并给其src 赋值。发送请求:当给script的src赋值时,浏览器就会发起一个请求。数据响应:服务端将要返回的数据作为参数和函数名称拼接在一起(格式类似”jsonpCallback({name: 'abc'})”)返回。当浏览器接收到了响应数据,由于发起请求的是 script,所以相当于直接调用 jsonpCallback 方法,并且传入了一个参数。JSONP只支持GET请求,AJAX支持GET和POST请求。

WebSocket:使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

CORS:跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

利用Nginx高效解决跨域问题

跨域分为简单跨域和复杂跨域。简单跨域不会发送OPTIONS请求,而复杂跨域会发送一个预检查OPTIONS请求。复杂跨域的条件是:

  • 非GET、HEAD、POST请求。
  • POST请求的Content-Type不是application/x-www-form-urlencoded, multipart/form-data, 或text/plain。
  • 添加了自定义header,例如Token。

跨域请求浏览器会在Headers中添加Origin,通常情况下不允许用户修改其值。关于同源策略,当请求了于自身域名不相同的url时,会出现如下信息提示错误:

1
Access to XMLHttpRequest at ‘***’ from origin ‘***’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

跨域代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#通配符* 全部允许 存在安全问题(不推荐)
server {
...
location / {
# 允许 所有头部 所有域 所有方法
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Methods' '*';
# OPTIONS 直接返回204
if ($request_method = 'OPTIONS') {
return 204;
}
}
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#多域名配置 配置多个域名在map中 只有配置过的允许跨域
map $http_origin $corsHost {
default 0;
"~https://yourdomain_1" https://yourdomain_1;
"~https://yourdomain_2" https://yourdomain_2;
"~https://yourdomain_3" https://yourdomain_3;
}
server {
...
location / {
# 允许 所有头部 所有$corsHost域 所有方法
add_header 'Access-Control-Allow-Origin' $corsHost;
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Methods' '*';
# OPTIONS 直接返回204
if ($request_method = 'OPTIONS') {
return 204;
}
}
...
}

CRUL跨域测试

GET请求成功返回跨域头:

1
2
3
4
5
6
7
8
9
10
~ curl -I -H "Origin: http://localhost" http://localhost
HTTP/1.1 403 Forbidden
Server: nginx/1.15.6
Date: Wed, 14 Nov 2018 07:56:01 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 153
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token

OPTIONS预检请求成功返回跨域头:

1
2
3
4
5
6
7
8
9
10
11
~ curl -I -H "Origin: http://localhost" -X OPTIONS http://localhost
HTTP/1.1 204 No Content
Server: nginx/1.15.6
Date: Wed, 14 Nov 2018 08:19:36 GMT
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Accept, Authorization, Cache-Control, Content-Type, DNT, If-Modified-Since, Keep-Alive, Origin, User-Agent, X-Requested-With, Token, x-access-token
Access-Control-Max-Age: 1728000
Content-Type: text/plain charset=utf-8
Content-Length: 0

跨域解决这个办法有很多:

  • 可以在被请求页面加上下面的代码,最好content填写域名。
    1
    <meta http-equiv="Access-Control-Allow-Origin" content="*">
  • 也可以在请求控制器加上加上下面的代码。
    1
    2
    3
    4
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Methods:GET, POST, OPTIONS, DELETE");
    header("Access-Control-Allow-Headers:DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,
    If-Modified-Since,Cache-Control,Content-Type, Accept-Language, Origin, Accept-Encoding");
  • IIS、Apache、Nginx可以直接配置Access-Control-Allow-Origin 跨域,具体如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // IIS配置:只需要在IIS添加HTTP响应标头即可!
    Access-Control-Allow-Headers:Content-Type, api_key, Authorization
    Access-Control-Allow-Origin:*
    // Apache配置:主要修改http.conf
    <Directory "/Users/cindy/dev">
    AllowOverride ALL
    Header set Access-Control-Allow-Origin *
    </Directory>
    // Nginx配置:主要是修改nginx.conf
    location /api {
    # 允许请求地址跨域 * 做为通配符
    add_header 'Access-Control-Allow-Origin' '*';
    # 设置请求方法跨域
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
    # 设置是否允许 cookie 传输
    add_header 'Access-Control-Allow-Credentials' 'true';
    # 设置请求头 这里为什么不设置通配符 * 因为不支持
    add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,
    Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Data-Type,X-Requested-With,X-Data-Type,X-Auth-Token';
    # 设置 options 请求处理,倘若Nginx配置跨域不生效,return 204或许是诱因之一。因为post 请求浏览器会发送一个 options 的预检请求,
    主要将本次的请求头发送给服务端,若服务端允许,再发送真正的post请求,所以F12 看到,经常post会发送两次请求。假如后端代码没有对
    options请求做出处理,导致options接口请求的时候,报 403 forbidden , 这里Nginx对options的请求直接返回200,不用到达接口层,
    直接允许post响应头,即可使得上述失效配置能够生效。
    if ( $request_method = 'OPTIONS' ) {
    return 200;
    }
    # 设置反向代理
    # proxy_pass 127.0.0.1:[$port];
    }