Nginx通過CORS實現跨域
什么是CORS
CORS是一個W3C標準,全稱是跨域資源共享(Cross-origin resource sharing)。它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
當前幾乎所有的瀏覽器(Internet Explorer 8+, Firefox 3.5+, Safari 4+和 Chrome 3+)都可通過名為跨域資源共享(Cross-Origin Resource Sharing)的協議支持AJAX跨域調用。
Chrome,Firefox,Opera,Safari都使用的是XMLHttpRequest2對象,IE使用XDomainRequest。
簡單來說就是跨域的目標服務器要返回一系列的Headers,通過這些Headers來控制是否同意跨域。跨域資源共享(CORS)也是未來的跨域問題的標準解決方案。
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-Origin這個Header, 它是W3C標準里用來檢查該跨域請求是否可以被通過。(Access Control Check)。如果需要跨域,解決方法就是在資源的頭中加入Access-Control-Allow-Origin 指定你授權的域。
啟用CORS請求
假設您的應用已經在example.com上了,而您想要從www.example2.com提取數據。一般情況下,如果您嘗試進行這種類型的AJAX調用,請求將會失敗,而瀏覽器將會出現源不匹配的錯誤。利用CORS后只需www.example2.com 服務端添加一個HTTP Response頭,就可以允許來自example.com的請求。
將Access-Control-Allow-Origin添加到某網站下或整個域中的單個資源
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Credentials: true (可選)
將允許任何域向您提交請求
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true (可選)
提交跨域請求
如果服務器端已啟用了CORS,那么提交跨域請求就和普通的XMLHttpRequest請求沒什么區別。例如現在example.com可以向www.example2.com提交請求。
var xhr = new XMLHttpRequest();
// xhr.withCredentials = true; //如果需要Cookie等
xhr.open('GET', 'http://www.example2.com/hello.json');
xhr.onload = function(e) {
var data = JSON.parse(this.response);
...
}
xhr.send();
服務端Nginx配置
要實現CORS跨域,服務端需要下圖中這樣一個流程
- 對于簡單請求,如GET,只需要在HTTP Response后添加Access-Control-Allow-Origin。
- 對于非簡單請求,比如POST、PUT、DELETE等,瀏覽器會分兩次應答。第一次preflight(method: OPTIONS),主要驗證來源是否合法,并返回允許的Header等。第二次才是真正的HTTP應答。所以服務器必須處理OPTIONS應答。
流程如下
- 首先查看http頭部有無origin字段;
- 如果沒有,或者不允許,直接當成普通請求處理,結束;
- 如果有并且是允許的,那么再看是否是preflight(method=OPTIONS);
- 如果是preflight,就返回Allow-Headers、Allow-Methods等,內容為空;
- 如果不是preflight,就返回Allow-Origin、Allow-Credentials等,并返回正常內容。
用偽代碼表示
location /pub/(.+) {
if ($http_origin ~ <允許的域(正則匹配)>) {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Credentials' "true";
if ($request_method = "OPTIONS") {
add_header 'Access-Control-Max-Age' 86400;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE';
add_header 'Access-Control-Allow-Headers' 'reqid, nid, host, x-real-ip, x-forwarded-ip, event-type, event-id, accept, content-type';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain, charset=utf-8';
return 204;
}
}
# 正常nginx配置
......
}</code></pre>
Nginx配置實例
實例一:允許example.com的應用在www.example2.com上跨域提取數據
在nginx.conf里找到server項,并在里面添加如下配置
location /{
add_header 'Access-Control-Allow-Origin' '如果需要允許來自任何域的訪問,可以這樣配置
add_header Access-Control-Allow-Origin *;
注釋如下
第一條指令:授權從example.com的請求(必需)
第二條指令:當該標志為真時,響應于該請求是否可以被暴露(可選)
第三條指令:允許腳本訪問的返回頭(可選)
第四條指令:指定請求的方法,可以是GET, POST, OPTIONS, PUT, DELETE等(可選)
重啟Nginx
$ service nginx reload
測試跨域請求
$ curl -I -X OPTIONS -H "Origin: http://example.com" http://www.example2.com
成功時,響應頭是如下所示
HTTP/1.1 200 OK
Server: nginx
Access-Control-Allow-Origin: example.com
實例二:Nginx允許多個域名跨域訪問
由于Access-Control-Allow-Origin參數只允許配置單個域名或者 * ,當我們需要允許多個域名跨域訪問時可以用以下幾種方法來實現。
- 方法一
如需要允許用戶請求來自www.example.com、m.example.com、wap.example.com訪問www.example2.com域名時,返回頭Access-Control-Allow-Origin,具體配置如下
在nginx.conf里面,找到server項,并在里面添加如下配置
map $http_origin $corsHost {
default 0;
"~http://www.example.com;
"~http://m.example.com;
"~http://wap.example.com;
}
server
{
listen 80;
server_name www.example2.com;
root /usr/share/nginx/html;
location /
{
add_header Access-Control-Allow-Origin $corsHost;
}
}</code></pre>
- 方法二
如需要允許用戶請求來自localhost、www.example.com或m.example.com的請求訪問xxx.example2.com域名時,返回頭Access-Control-Allow-Origin,具體配置如下
在Nginx配置文件中xxx.example2.com域名的location /下配置以下內容
set $cors '';
if ($http_origin ~* 'https?://(localhost|www.example.com|m.example.com)') {
set $cors 'true';
}
if ($cors = 'true') {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With';
}
if ($request_method = 'OPTIONS') {
return 204;
}</code></pre>
- 方法三
如需要允許用戶請求來自*.example.com訪問xxx.example2.com域名時,返回頭Access-Control-Allow-Origin,具體配置如下
在Nginx配置文件中xxx.example2.com域名的location /下配置以下內容
if ( $http_origin ~ http://(.*).example.com){
set $allow_url $http_origin;
}
#CORS(Cross Orign Resource-Sharing)跨域控制配置
#是否允許請求帶有驗證信息
add_header Access-Control-Allow-Credentials true;
#允許跨域訪問的域名,可以是一個域的列表,也可以是通配符*
add_header Access-Control-Allow-Origin $allow_url;
#允許腳本訪問的返回頭
add_header Access-Control-Allow-Headers 'x-requested-with,content-type,Cache-Control,Pragma,Date,x-timestamp';
#允許使用的請求方法,以逗號隔開
add_header Access-Control-Allow-Methods 'POST,GET,OPTIONS,PUT,DELETE';
#允許自定義的頭部,以逗號隔開,大小寫不敏感
add_header Access-Control-Expose-Headers 'WWW-Authenticate,Server-Authorization';
#P3P支持跨域cookie操作
add_header P3P 'policyref="/w3c/p3p.xml", CP="NOI DSP PSAa OUR BUS IND ONL UNI COM NAV INT LOC"';</code></pre>
- 方法四
如需要允許用戶請求來自xxx1.example.com或xxx1.example1.com訪問xxx.example2.com域名時,返回頭Access-Control-Allow-Origin,具體配置如下
在Nginx配置文件中xxx.example2.com域名的location /下配置以下內容
location / {
if ( $http_origin ~ .*.(example|example1).com ) {
add_header Access-Control-Allow-Origin $http_origin;
}
}</code></pre>
實例三:Nginx跨域配置并支持DELETE,PUT請求
默認Access-Control-Allow-Origin開啟跨域請求只支持GET、HEAD、POST、OPTIONS請求,使用DELETE發起跨域請求時,瀏覽器出于安全考慮會先發起OPTIONS請求,服務器端接收到的請求方式就變成了OPTIONS,所以引起了服務器的405 Method Not Allowed。
解決方法
首先要對OPTIONS請求進行處理
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
#其他頭部信息配置,省略...
return 204;
}</code></pre>
當請求方式為OPTIONS時設置Allow的響應頭,重新處理這次請求。這樣發出請求時第一次是OPTIONS請求,第二次才是DELETE請求。
# 完整配置參考
將配置文件的放到對應的server {}里
add_header Access-Control-Allow-Origin *;
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
return 204;
}
index index.php;
try_files $uri @rewriteapp;
}</code></pre>
實例四:更多配置示例
- 示例一
The following Nginx configuration enables CORS, with support for preflight requests.
#
Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}</code></pre>
- 示例二
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://docs.domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,token';
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' 'https://docs.domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,token';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' 'https://docs.domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,token';
}
其它技巧
Apache中啟用CORS
在httpd配置或.htaccess文件中添加如下語句
SetEnvIf Origin "^(.*\.example\.com)$" ORIGIN_SUB_DOMAIN=$1
Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
PHP中啟用CORS
通過在服務端設置Access-Control-Allow-Origin響應頭
- 允許所有來源訪問
<?php
header("Access-Control-Allow-Origin: *");
?>
- 允許來自特定源的訪問
<?php
header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
?>
- 配置多個訪問源
由于瀏覽器實現只支持了單個origin、*、null,如果要配置多個訪問源,可以在代碼中處理如下
<?php
$allowed_origins = array(
"http://www.example.com" ,
"http://app.example.com" ,
"http://cms.example.com" ,
);
if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)){
@header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
}
?>
HTML中啟用CORS
<meta http-equiv="Access-Control-Allow-Origin" content="*">
參考文檔
http://www.google.com
http://t.cn/RZEYPmD
http://t.cn/RhcAN2d
http://to-u.xyz/2016/06/30/nginx-cors/
http://coderq.github.io/2016/05/13/cross-domain/
來自:http://www.yunweipai.com/archives/9381.html