Nginx經過CORS實現跨域(轉)

若是前端有nginx方向代理,跨域配置在前端反向代理nginx上php

要作跨域域名限制html

什麼是CORS

CORS是一個W3C標準,全稱是跨域資源共享(Cross-origin resource sharing)。它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。前端

當前幾乎全部的瀏覽器(Internet Explorer 8+, Firefox 3.5+, Safari 4+和 Chrome 3+)均可經過名爲跨域資源共享(Cross-Origin Resource Sharing)的協議支持AJAX跨域調用。nginx

Chrome,Firefox,Opera,Safari都使用的是XMLHttpRequest2對象,IE使用XDomainRequest。git

簡單來講就是跨域的目標服務器要返回一系列的Headers,經過這些Headers來控制是否贊成跨域。跨域資源共享(CORS)也是將來的跨域問題的標準解決方案。github

CORS提供以下Headers,Request包和Response包中都有一部分。json

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跨域,服務端須要下圖中這樣一個流程

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配置
    ......
}

Nginx配置實例

實例一:容許example.com的應用在www.example2.com上跨域提取數據

在nginx.conf裏找到server項,並在裏面添加以下配置

location /{

add_header 'Access-Control-Allow-Origin' 'http://example.com';
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-Requested-With';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
...
}

若是須要容許來自任何域的訪問,能夠這樣配置

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://www.example.com;
    "~http://m.example.com" http://m.example.com;
    "~http://wap.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;
    }
}
  • 方法二

如須要容許用戶請求來自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;
}
  • 方法三

如須要容許用戶請求來自*.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"';
  • 方法四

如須要容許用戶請求來自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;
    }
}

實例三: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; 
}

當請求方式爲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;
}

實例四:更多配置示例

  • 示例一
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';
     }
}
  • 示例二
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/

相關文章
相關標籤/搜索