美文网首页node
CORS跨域服务器设置

CORS跨域服务器设置

作者: _leopold | 来源:发表于2018-09-17 13:53 被阅读0次

    CORS即Cross-Origin Resource Sharing,跨域资源共享

    CORS分为两种

    一:简单的跨域请求,流程如下

    网页:当HTTP请求同时满足以下两种情况时,浏览器认为是简单跨请求

    1),请求的方法是get,head或者post,同时Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一个值,或者不设置也可以,一般默认就是application/x-www-form-urlencoded。

    2),请求中没有自定义的HTTP头部,如x-token。(应该是这几种头部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)

    浏览器:把客户端脚本所在的域填充到Origin header里,向其他域的服务器请求资源。

    服务器:根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header,返回结果

    浏览器:比较服务器返回的Access-Control-Allow-Origin Header和请求域的Origin,如果当前域已经得到授权,则将结果返回给页面。否则浏览器忽略此次响应。

    网页:收到返回结果或者浏览器的错误提示。

    总结:对于简单的跨域请求,只要服务器设置的Access-Control-Allow-Origin Header和请求来源匹配,浏览器就允许跨域。服务器端设置的Access-Control-Allow-Methods和Access-Control-Allow-Headers对简单跨域没有作用。

    二:带预检(Preflighted)的跨域请求,流程如下

    网页:当HTTP请求出现以下两种情况时之一,浏览器认为是带预检(Preflighted)的跨域请求:

    1),请求的方法不是 GET, HEAD或者POST三种,或者是这三种,但是Content-Type不是application/x-www-form-urlencoded, multipart/form-data或text/plain中的一种。

    2),请求中有自定义HTTP头部。

    浏览器:发现请求属于以上两种情况,向服务器发送一个OPTIONS预检请求,检测服务器端是否支持真实请求进行跨域资源访问,浏览器会在发送OPTIONS请求时会自动添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。

    服务器:响应OPTIONS请求,会在responseHead里添加Access-Control-Allow-Methods head。这其中的method的值是服务器给的默认值,可能不同的服务器添加的值不一样。服务器还会添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。这些取决于服务器对OPTIONS请求具体如何做出响应。如果服务器对OPTIONS响应不合你的要求,你可以手动在服务器配置OPTIONS响应,以应对带预检的跨域请求。在配置服务器OPTIONS的响应时,可以添加Access-Control-Max-Age head告诉浏览器在一定时间内无需再次发送预检请求,但是如果浏览器禁用缓存则无效。

    浏览器:接到OPTIONS的响应,比较真实请求的method是否属于返回的Access-Control-Allow-Methods head的值之一,还有origin, head也会进行比较是否匹配。如果通过,浏览器就继续向服务器发送真实请求。 否则就会报预检错误,如下几种:

      请求来源不被options响应允许:Failed to load...Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin http://127.0.0.1:8080 is therefore not allowed access.

      请求方法不被options响应允许:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

      请求中有自定义header不被options响应允许:Failed to load... Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.

    服务器:响应真实请求,在响应头中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头,并返回数据。(如果服务器对真实请求的响应另外设置有Access-Control-Allow-Methods,它的值不会生效,个人理解是因为刚刚在服务器响应OPTIONS响应时,就已经验证过真实请求的method是属于Access-Control-Allow-Methods head的值之一)。也可以在响应真实请求时添加Access-Control-Max-Age head。

    浏览器:接受服务器对真实请求的返回结果,返回给网页

    网页:收到返回结果或者浏览器的错误提示。

    总结:也就是说Access-Control-Allow-Methods和Access-Control-Allow-Headers只在响应options请求时有作用,Access-Control-Allow-Origin在响应options请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。

    服务器设置OPTIONS响应一般要同时满足这些条件,一是跨域,二是有带预检的请求,三是服务器对OPTIONS响应默认值不符合要求,如果是不存在跨域情况,就不需要在服务器手动设置OPTIONS响应。

    image image

    XMLHttpRequest支持通过withCredentials属性跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)。浏览器将携带Cookie Header的请求发送到服务器端后,如果服务器没有响应Access-Control-Allow-Credentials Header,那么浏览器会忽略掉这次响应。如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名。

    express框架跨域设置:

    // 以node-express框架为例
    const app = require('express')();
    app.options('/', (req, res) => {
    //express框架有res.set()和res.header()两种方式设置header,没有setHeader方法。
        res.set({
            "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
            'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST',
            'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type',
            'Access-Control-Max-Age':10000,
            'Access-Control-Allow-Credentials':true
        });
        const obj = {
            "msg": "options请求"
        }
        res.send(obj)
    })
     
    app.post('/', (req, res) => {
        console.log('post请求')
        res.set({
            "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
      // 'Access-Control-Allow-Methods': 'POST',//无需设置。因为如果是带预检的跨域请求时,是否是允许的该请求方法取决于options请求响应时的response head里的access-control-allow-methods head.如果是简单的跨域请求,只有Access-Control-Allow-Origin会参与匹配,此设置依然没有作用。
    // 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type,A',//不需设置,原因同上。
        });
        const obj = {
            "msg": "post请求"
        }
        res.send(obj)
    })
     
    app.get('/', (req, res, next) => {
        console.log('get请求')
        res.set({
            "Access-Control-Allow-Origin": "http://127.0.0.1:8080",
        });
        const obj = {
            msg: 'get请求'
        }
        res.send(obj)
    })
     
    app.put('/', (req, res) => {
        res.set({
            "Access-Control-Allow-Origin": "http://127.0.0.1:8080"
        });
        const obj = {
            "msg": "put请求"
        }
        res.send(obj)
    })
    app.listen(3333, function () {
        console.log('express start at port 3333')
    })
    

    koa框架跨域设置:

    //以node-koa框架为例
    const Koa = require('koa');
    const app = new Koa();
    const _cors=(ctx,next)=>{
        //指定服务器端允许进行跨域资源访问的来源域。可以用通配符*表示允许任何域的JavaScript访问资源,但是在响应一个携带身份信息(Credential)的HTTP请求时,必需指定具体的域,不能用通配符
        ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:8080");
     
        //指定服务器允许进行跨域资源访问的请求方法列表,一般用在响应预检请求上
        ctx.set("Access-Control-Allow-Methods", "OPTIONS,POST,GET,HEAD,DELETE,PUT");
        
        //必需。指定服务器允许进行跨域资源访问的请求头列表,一般用在响应预检请求上
        ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
        
        //告诉客户端返回数据的MIME的类型,这只是一个标识信息,并不是真正的数据文件的一部分
        ctx.set("Content-Type", "application/json;charset=utf-8");
        
        //可选,单位为秒,指定浏览器在本次预检请求的有效期内,无需再发送预检请求进行协商,直接用本次协商结果即可。当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证
        ctx.set("Access-Control-Max-Age", 300);
     
        //可选。它的值是一个布尔值,表示是否允许客户端跨域请求时携带身份信息(Cookie或者HTTP认证信息)。默认情况下,Cookie不包括在CORS请求之中。当设置成允许请求携带cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*";如果没有设置这个值,浏览器会忽略此次响应。
        ctx.set("Access-Control-Allow-Credentials", true);
     
        //可选。跨域请求时,客户端xhr对象的getResponseHeader()方法只能拿到6个基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。要获取其他字段时,使用Access-Control-Expose-Headers,xhr.getResponseHeader('myData')可以返回我们所需的值
        ctx.set("Access-Control-Expose-Headers", "myData");
     
        next()
    }
    const main = function (ctx) {
        const _method=ctx.request.method;
        ctx.response.body={"请求方式":_method};
    };
     
    app.use(_cors)
    app.use(main)
     
    app.listen(5000, function () {
        console.log('koa start at port 5000')
    })
    
    // 前台代码,用jqAjax和axios两种请求方式对比
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <table>
          <tr>
              <th col="2">Server</th>
          </tr>
        <tr>
          <th><button id="express" class="server" onclick='changeServer("http://localhost:3333",this)'>express_3333</button></th>
          <th><button id="koa" class="server" onclick='changeServer("http://localhost:5000",this)'>koa_5000</button></th>
        </tr>
      </table>
     
      <table>
        <tr>
          <th col="4">content-Type</th>
        </tr>
        <td><button id="x-www-form-urlencoded" class="ContentType" onclick='changeContentType("application/x-www-form-urlencoded",this)'>application/x-www-form-urlencoded</button></td>
        <td><button class="ContentType" onclick='changeContentType("multipart/form-data",this)'>multipart/form-data</button></td>
        <td><button class="ContentType" onclick='changeContentType("text/plain",this)'>text/plain</button></td>
        <td><button class="ContentType" onclick='changeContentType("application/json",this)'>application/json</button></td>
      </table>
     
      <table>
     
        <tr>
            <tr>
                <th col="2">Method</th>
            </tr>
          <td>JQuery</td>
          <td>axios</td>
        </tr>
        <tr>
          <td><button onclick='jq_request("GET")'>jq_get</button></td>
          <td><button onclick='axios_request("GET")'>axios_get</button></td>
        </tr>
        <tr>
            <td><button onclick='jq_request("HEAD")'>jq_head</button></td>
            <td><button onclick='axios_request("HEAD")'>axios_head</button></td>
          </tr>
        <tr>
          <td><button onclick='jq_request("POST")'>jq_post</button></td>
          <td><button onclick='axios_request("POST")'>axios_post</button></td>
        </tr>
        <tr>
          <td><button onclick='jq_request("PUT")'>jq_put</button></td>
          <td><button onclick='axios_request("PUT")'>axios_put</button></td>
        </tr>
        <tr>
          <td><button onclick='jq_request("DELETE")'>jq_delete</button></td>
          <td><button onclick='axios_request("DELETE")'>axios_delete</button></td>
        </tr>
        <tr>
            <td><button onclick='jq_request("OPTIONS")'>jq_options</button></td>
            <td><button onclick='axios_request("OPTIONS")'>axios_options</button></td>
          </tr>
      </table>
     
    </body>
    <script src="https://cdn.bootcss.com/jquery/3.3.0/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
    <script>
     
      let url='http://localhost:3333';
      let contentType="application/x-www-form-urlencoded";
     
      const changeServer=(a,self)=>{
        url=a;
        $('.server').css('background','#eee')
        $(self).css('background','gray')
      }
     
      const changeContentType=(e,self)=>{
        contentType=e;
        $('.ContentType').css('background','#eee')
        $(self).css('background','gray')
      }
     
      $('#koa').click();
      $('#x-www-form-urlencoded').click();
     
      const jq_request = (method) => {
        $.ajax({
          url: url,
          type: method,
          contentType:contentType,
          dataType: "json",
          success: function (data) {
            console.log(data)
          },
          error: function (err) {
            console.log(err)
          }
        })
      }
     
      const axios_request=(method)=>{
        axios({
          method: method,
          url: url,
          headers: {
            'Content-Type':contentType,
          },
     
          responseType: 'json',
        }).then(res => {
          console.log(res.data)
        }).catch(error => {
          console.log(error);
        });
      }
    </script>
    </html>
    

    参考了以下几篇博客:
    https://blog.csdn.net/enter89/article/details/51205752

    https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/CORS%E8%AF%A6%E8%A7%A3.md

    https://www.cnblogs.com/MrZouJian/p/8568414.html

    https://www.jianshu.com/p/5b3acded5182

    欢迎补充,讨论。转载或引用请注明出处

    相关文章

      网友评论

        本文标题:CORS跨域服务器设置

        本文链接:https://www.haomeiwen.com/subject/lxdlnftx.html