之前项目中遇到过几次跨域访问,通过百度或谷歌查询在nginx配置相关header予以解决,若不管用就没其他办法了;从来没有真正深入了解过,下面我们就来认识下CORS及在nginx中如何配置。
CORS是一个W3C标准,全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
简单来说就是跨域的目标服务器要返回一系列的Headers,通过这些Headers来控制是否同意跨域。
CORS提供的Headers,在Request包和Response包中都有一部分:
#HTTP Response Header
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Expose-Headers
Access-Control-Max-Age
#HTTP Request Header
Access-Control-Request-Method
Access-Control-Request-Headers
其中Access-Control-Allow-Headers一般包含基本字段,如Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma;对于其他字段,就必须在Access-Control-Expose-Headers里面指定。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
1.简单请求
只要同时满足以下两大条件,就属于简单请求
1.请求方法是以下三种方法之一:
HEAD
GET
POST
2.HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
2.非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT、DELETE或OPTIONS,或者Content-Type字段的类型是application/json。
为什么要讲简单请求和非简单请求呢?因为浏览器对对这两种请求的处理,是不一样的。
1.简单请求
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
处理如下:
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
在nginx上经以上配置,一般能够解决简单请求的跨域处理。
CORS请求默认不发送Cookie和HTTP认证信息。但是如果要把Cookie发到服务器,要服务器同意,指定Access-Control-Allow-Credentials字段。
add_header 'Access-Control-Allow-Credentials' 'true';
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。
2.非简单请求
(1)非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight),预捡的请求方法是OPTIONS。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。
因此我们可以在浏览器的开发者工具中查看头信息,若头信息中有OPTIONS方法,说明此次CORS请求是非简单请求,需要在nginx中添加头:
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
此时虽然我们已经添加了所有的Methods方法,但是预检请求仍不会通过,因此此时nginx对OPTIONS方法返回"405 Method Not Allowed”或者403。我们需要在nginx对OPTIONS方法进行处理,如下:
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
return 200;
}
或
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
#在location处添加以下内容
if ($request_method = 'OPTIONS') {
return 200;
}
我们在nginx上将OPTIONS方法返回200,而不是405或403。
(2)服务器经过以上设置通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。
Nginx标准配置
server {
... ...
# #设置跨域配置 Start
set $cors_origin "";
if ($http_origin ~* "^http://api.xx.com$"){
set $cors_origin $http_origin;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,x-auth-token always;
add_header Access-Control-Max-Age 1728000 always;
# 预检请求处理
if ($request_method = OPTIONS) {
return 204;
}
# #设置跨域配置 End
... ...
}
1.设置Origin:表示服务器可以接受的请求
add_header Access-Control-Allow-Origin http://api.baidu.com
表示 http://api.baidu.com
可以请求数据。这个可以设置为*星号
代表任意跨源请求都支持,但不建议这样设置;因为设置为*星号
将不在支持发送Cookie。
2.设置多域名配置
set $cors_origin "";
if ($http_origin ~* "^http://api.xx.com$"){
set $cors_origin $http_origin;
}
if ($http_origin ~* "^http://api2.xx.com$"){
set $cors_origin $http_origin;
}
这个写法主要是为了支持多域名设置,通过对请求origin的判断是否与指定跨域源一致,然后在进行header的设置;
3.设置跨域支持的请求类型
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS always;
4.设置跨域请求是否允许发送Cookie,true:支持,false:不支持
add_header Access-Control-Allow-Credentials true always;
5.设置跨域请求允许的Header头信息字段,以逗号分隔的字符串
add_header Access-Control-Allow-Headers DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,x-auth-token always;
注:需要特别注意,凡是API中约定了自定义Header,都需要在这里增加,否则不可以跨域请求。
6.本次预检请求的有效期,单位为秒,在允许缓存该条请求回应1728000秒内,无需在发出一条预检请求。
add_header Access-Control-Max-Age 1728000 always;
7.always 参数的定义
... ... always
Nginx 规则 add_header默认只会加入到指定response code
的请求中;
见官网介绍:
Syntax: add_header name value [always];
Default: —
Context: http, server, location, if in location
Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). The value can contain variables.
指定了 always
则无论什么请求都添加header
:
If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.
8.预检请求处理
if ($request_method = OPTIONS) {
return 204;
}
CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight);浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
"预检"请求用的请求方法是OPTIONS
,表示这个请求是用来询问的,因此我们需要在Nginx配置中,针对预检请求进行处理,直接返回204 & Response Header,表示服务器支持允许跨源的访问。
AJAX配置
xhr.withCredentials = false;
CORS请求默认不发送Cookie和HTTP认证信息,如果需要支持除了服务端增加设置,AJAX请求中打开withCredentials
属性。
参考:
http://www.ruanyifeng.com/blog/2016/04/cors.html
http://www.yunweipai.com/