Nginx通过CORS实现跨域

wufn0087 8年前
   <h2>什么是CORS</h2>    <p>CORS是一个W3C标准,全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。</p>    <p>当前几乎所有的浏览器(Internet Explorer 8+, Firefox 3.5+, Safari 4+和 Chrome 3+)都可通过名为跨域资源共享(Cross-Origin Resource Sharing)的协议支持AJAX跨域调用。</p>    <p>Chrome,Firefox,Opera,Safari都使用的是XMLHttpRequest2对象,IE使用XDomainRequest。</p>    <p>简单来说就是跨域的目标服务器要返回一系列的Headers,通过这些Headers来控制是否同意跨域。跨域资源共享(CORS)也是未来的跨域问题的标准解决方案。</p>    <p>CORS提供如下Headers,Request包和Response包中都有一部分。</p>    <h3>HTTP Response Header</h3>    <ul>     <li>Access-Control-Allow-Origin</li>     <li>Access-Control-Allow-Credentials</li>     <li>Access-Control-Allow-Methods</li>     <li>Access-Control-Allow-Headers</li>     <li>Access-Control-Expose-Headers</li>     <li>Access-Control-Max-Age</li>    </ul>    <h3>HTTP Request Header</h3>    <ul>     <li>Access-Control-Request-Method</li>     <li>Access-Control-Request-Headers</li>    </ul>    <p>其中最敏感的就是Access-Control-Allow-Origin这个Header, 它是W3C标准里用来检查该跨域请求是否可以被通过。(Access Control Check)。如果需要跨域,解决方法就是在资源的头中加入Access-Control-Allow-Origin 指定你授权的域。</p>    <h2>启用CORS请求</h2>    <p>假设您的应用已经在example.com上了,而您想要从www.example2.com提取数据。一般情况下,如果您尝试进行这种类型的AJAX调用,请求将会失败,而浏览器将会出现源不匹配的错误。利用CORS后只需www.example2.com 服务端添加一个HTTP Response头,就可以允许来自example.com的请求。</p>    <p>将Access-Control-Allow-Origin添加到某网站下或整个域中的单个资源</p>    <pre>  <code class="language-javascript">Access-Control-Allow-Origin: http://example.com  Access-Control-Allow-Credentials: true (可选)</code></pre>    <p>将允许任何域向您提交请求</p>    <pre>  <code class="language-javascript">Access-Control-Allow-Origin: *  Access-Control-Allow-Credentials: true (可选)</code></pre>    <h2>提交跨域请求</h2>    <p>如果服务器端已启用了CORS,那么提交跨域请求就和普通的XMLHttpRequest请求没什么区别。例如现在example.com可以向www.example2.com提交请求。</p>    <pre>  <code class="language-javascript">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();</code></pre>    <h2>服务端Nginx配置</h2>    <p>要实现CORS跨域,服务端需要下图中这样一个流程</p>    <p><img src="https://simg.open-open.com/show/118dc50a0de120e5047f5921be5a4a25.jpg"></p>    <ul>     <li>对于简单请求,如GET,只需要在HTTP Response后添加Access-Control-Allow-Origin。</li>     <li>对于非简单请求,比如POST、PUT、DELETE等,浏览器会分两次应答。第一次preflight(method: OPTIONS),主要验证来源是否合法,并返回允许的Header等。第二次才是真正的HTTP应答。所以服务器必须处理OPTIONS应答。</li>    </ul>    <p>流程如下</p>    <ul>     <li>首先查看http头部有无origin字段;</li>     <li>如果没有,或者不允许,直接当成普通请求处理,结束;</li>     <li>如果有并且是允许的,那么再看是否是preflight(method=OPTIONS);</li>     <li>如果是preflight,就返回Allow-Headers、Allow-Methods等,内容为空;</li>     <li>如果不是preflight,就返回Allow-Origin、Allow-Credentials等,并返回正常内容。</li>    </ul>    <p>用伪代码表示</p>    <pre>  <code class="language-javascript">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>    <h2>Nginx配置实例</h2>    <h3>实例一:允许example.com的应用在www.example2.com上跨域提取数据</h3>    <p>在nginx.conf里找到server项,并在里面添加如下配置</p>    <pre>  <code class="language-javascript">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';  ...  }</code></pre>    <p>如果需要允许来自任何域的访问,可以这样配置</p>    <pre>  <code class="language-javascript">add_header Access-Control-Allow-Origin *;</code></pre>    <p>注释如下</p>    <p>第一条指令:授权从example.com的请求(必需)</p>    <p>第二条指令:当该标志为真时,响应于该请求是否可以被暴露(可选)</p>    <p>第三条指令:允许脚本访问的返回头(可选)</p>    <p>第四条指令:指定请求的方法,可以是GET, POST, OPTIONS, PUT, DELETE等(可选)</p>    <p>重启Nginx</p>    <pre>  <code class="language-javascript">$ service nginx reload</code></pre>    <p>测试跨域请求</p>    <pre>  <code class="language-javascript">$ curl -I -X OPTIONS -H "Origin: http://example.com" http://www.example2.com</code></pre>    <p>成功时,响应头是如下所示</p>    <pre>  <code class="language-javascript">HTTP/1.1 200 OK  Server: nginx  Access-Control-Allow-Origin: example.com</code></pre>    <h3>实例二:Nginx允许多个域名跨域访问</h3>    <p>由于Access-Control-Allow-Origin参数只允许配置单个域名或者 * ,当我们需要允许多个域名跨域访问时可以用以下几种方法来实现。</p>    <ul>     <li>方法一</li>    </ul>    <p>如需要允许用户请求来自www.example.com、m.example.com、wap.example.com访问www.example2.com域名时,返回头Access-Control-Allow-Origin,具体配置如下</p>    <p>在nginx.conf里面,找到server项,并在里面添加如下配置</p>    <pre>  <code class="language-javascript">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;      }  }</code></pre>    <ul>     <li>方法二</li>    </ul>    <p>如需要允许用户请求来自localhost、www.example.com或m.example.com的请求访问xxx.example2.com域名时,返回头Access-Control-Allow-Origin,具体配置如下</p>    <p>在Nginx配置文件中xxx.example2.com域名的location /下配置以下内容</p>    <pre>  <code class="language-javascript">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>    <ul>     <li>方法三</li>    </ul>    <p>如需要允许用户请求来自*.example.com访问xxx.example2.com域名时,返回头Access-Control-Allow-Origin,具体配置如下</p>    <p>在Nginx配置文件中xxx.example2.com域名的location /下配置以下内容</p>    <pre>  <code class="language-javascript">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>    <ul>     <li>方法四</li>    </ul>    <p>如需要允许用户请求来自xxx1.example.com或xxx1.example1.com访问xxx.example2.com域名时,返回头Access-Control-Allow-Origin,具体配置如下</p>    <p>在Nginx配置文件中xxx.example2.com域名的location /下配置以下内容</p>    <pre>  <code class="language-javascript">location / {        if ( $http_origin ~ .*.(example|example1).com ) {      add_header Access-Control-Allow-Origin $http_origin;      }  }</code></pre>    <h3>实例三:Nginx跨域配置并支持DELETE,PUT请求</h3>    <p>默认Access-Control-Allow-Origin开启跨域请求只支持GET、HEAD、POST、OPTIONS请求,使用DELETE发起跨域请求时,浏览器出于安全考虑会先发起OPTIONS请求,服务器端接收到的请求方式就变成了OPTIONS,所以引起了服务器的405 Method Not Allowed。</p>    <p>解决方法</p>    <p>首先要对OPTIONS请求进行处理</p>    <pre>  <code class="language-javascript">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>    <p>当请求方式为OPTIONS时设置Allow的响应头,重新处理这次请求。这样发出请求时第一次是OPTIONS请求,第二次才是DELETE请求。</p>    <pre>  <code class="language-javascript"># 完整配置参考  # 将配置文件的放到对应的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>    <h3>实例四:更多配置示例</h3>    <ul>     <li>示例一</li>    </ul>    <pre>  <code class="language-javascript">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>    <ul>     <li>示例二</li>    </ul>    <pre>  <code class="language-javascript">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';    }</code></pre>    <h2>其它技巧</h2>    <h3>Apache中启用CORS</h3>    <p>在httpd配置或.htaccess文件中添加如下语句</p>    <pre>  <code class="language-javascript">SetEnvIf Origin "^(.*\.example\.com)$" ORIGIN_SUB_DOMAIN=$1    Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN</code></pre>    <h3>PHP中启用CORS</h3>    <p>通过在服务端设置Access-Control-Allow-Origin响应头</p>    <ul>     <li>允许所有来源访问</li>    </ul>    <pre>  <code class="language-javascript"><?php  header("Access-Control-Allow-Origin: *");  ?></code></pre>    <ul>     <li>允许来自特定源的访问</li>    </ul>    <pre>  <code class="language-javascript"><?php  header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);  ?></code></pre>    <ul>     <li>配置多个访问源</li>    </ul>    <p>由于浏览器实现只支持了单个origin、*、null,如果要配置多个访问源,可以在代码中处理如下</p>    <pre>  <code class="language-javascript"><?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']);    }  ?></code></pre>    <h2>HTML中启用CORS</h2>    <pre>  <code class="language-javascript"><meta http-equiv="Access-Control-Allow-Origin" content="*"></code></pre>    <h3>参考文档</h3>    <p>http://www.google.com</p>    <p>http://t.cn/RZEYPmD</p>    <p>http://t.cn/RhcAN2d</p>    <p>http://to-u.xyz/2016/06/30/nginx-cors/</p>    <p>http://coderq.github.io/2016/05/13/cross-domain/</p>    <p> </p>    <p> </p>    <p>来自:http://www.yunweipai.com/archives/9381.html</p>    <p> </p>