以express和koa框架为例,说明nodejs服务器CORS跨域怎么设置

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-MethodsAccess-Control-Allow-Headers只在响应options请求时有作用,Access-Control-Allow-Origin在响应options请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。

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

 

 

 

 

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

 

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

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值