cors笔记(开发中最常用,安全性高,主要靠服务端做手脚)

什么是CORS?

CORS即“跨域资源共享”,这是一种最常用的跨域实现方式,一般需要后端人员在处理请求数据的时候,添加允许跨域的相关请求头信息。大致思路是这样的:首先获取请求对象的信息,比如Origin字段,通过预先配置的参数判断请求是否合法,然后设置相应对象response的头信息,实现跨域资源请求。

响应头设置

  • Access-Control-Allow-Origin
  • 含义:允许哪个源可以访问
  • 实际操作:
  1. 后端一般会设置一个白名单
  2. 当前端发请求到后端的时候,后端会判断是否在白名单里,如果在,则在响应头里设置该头:
 res.setHeader('Access-Control-Allow-Origin',前端的域);
  • Access-Control-Allow-Headers
  • 含义:允许携带哪个头访问,即允许前端设置的自定义头字段传递数据
  • 实际操作:
  1. 前端需要通过头信息带一个字段和值给后端,比如在requestHeader中带name为yuhua给后端
  2. 则前端ajax可以如下操作:
 xhr.setRequestHeader('name','yuhua');//xhr为ajax对象,通过new XMLHttpRequest得到
  1. 当前端发送请求给后端的时候,后端需要在响应头里设置该头:
 res.setHeader('Access-Control-Allow-Headers','name')
  • 如果值设置为* ,那么再写凭证的头(Access-Control-Allow-Credentials)就会报错
  • Access-Control-Allow-Methods、Access-Control-Max-Age
  • 含义:前者:允许携带哪个方法访问,即允许所设置的请求方式请求;后者:允许预检存货时间
  • 实际操作:
  1. 假设前端想要通过PUT请求
  2. 当前端发请求到后端的时候,后端一般会设置允许的请求方式PUT,如下:
 res.setHeader('Access-Control-Allow-Methods','PUT')
预检请求
什么是预检请求?
  • 浏览器会将CORS请求分为两类:简单请求和非简单请求,简单请求浏览器不做预检,非简单请求才做预检。
  • 对于非简单请求,在正式跨域之前,浏览器会根据需要发起一次预检(也就是option请求),用来让服务端返回允许的方法(如get、post),被跨域访问的Origin(来源或者域),还有是否需要Credentials(认证信息)等
  • 一般类似PUT这类请求,会先发送一个OPTIONS的预检请求,如果预检请求OK,那么再发送PUT正式请求
如何处理OPTIONS请求?
  • 所以,如果是OPTIONS请求,那就不做任何处理
 if(req.method === 'OPTIONS'){
  res.end();
 }
如何设置OPTION请求频率?
  • 可以设置Access-Control-Max-Age头来设置多少秒之内不发请求
 res.setHeader("Access-Control-Max-Age",6);//以秒为单位,6s之内不会再发预检请求
什么情况下会发送OPTIONS预检请求?
  • 不符合以下条件的都会产生OPTIONS预检请求
  1. 请求方式只能是GET、POST、HEAD
  2. HTTP请求头限制这几种字段:Accept、Accept-Languag、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width
  3. Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain

以上就是简单请求的要求,除了这些就是非简单请求,关于简单请求请看文章https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests

  • Access-Control-Allow-Credentials
  • 含义:允许携带cookie、authorization等,及携带凭证请求头。即响应头表示是否可以将对请求的响应暴露给页面。返回true则可以,其他值均不可以。
  • 实际操作:
  1. 举例前端需要带cookie到后端,比如前端设置了如下cookie
 document.cookie = 'name=yuhua';
  1. 那么,前端如果直接发请求给后端,即便前端带上了cookie,那后端也是拿不到的。需要带上凭证请求头。具体如下:
 //前端ajax设置
 xhr.withCredentials = true
 //后端设置
 res.setHeader("Access-Control-Allow-Credentials",true);
什么时候需要带这个Credentials?

Credentials可以是 cookies, authorization headers 或 TLS client certificates。

特别注意

当作为对预检请求的响应的一部分时,这能表示是否真正的请求可以使用credentials。注意简单的GET 请求没有预检,所以若一个对资源的请求带了credentials,如果这个返回这个资源,响应就会被浏览器忽视,不会返回到web内容。

  • 注意cookie不允许跨域

  • 如果Access-Control-Allow-Headers值设置为* ,那么再写凭证的头(Access-Control-Allow-Credentials)就会报错

  • Access-Control-Expose-Headers

  • 含义:允许前端获取哪个头,即设置允许后端向前端暴露在response的请求头里的字段及值

  • 实际操作:

  1. 假设后端向对前端返回一个自定义头name,值为yuhua,可以如下操作
 res.setHeader('name','yuhua');
  1. 但是这种方式有问题,前端会报unsafe错误。那么该如何设置才能正确呢?应该再后端加上Access-Control-Expose-Headers头
 res.setHeader("Access-Control-Expose-Headers","name");
  1. 这样,再res.setHeader就可以了,前端可以通过以下字段拿到:
 xhr.getResponseHeader("name");

如何得到后端返回的头信息?

  1. 后端设置两点
  • 设置Access-Control-Expose-Headers
 res.setHeader("Access-Control-Expose-Headers","name");
  • 设置返回头的值
 res.setHeader('name','yuhua');
  1. 前端用如下方式得到:
 xhr.getResponseHeader("头字段")

缺点

  • 不兼容

注意事项:

  • 一般以上头都设置在后端请求的中间件中,举例如下:
 let express = require("express");
 let app = express();
 let whiteList = ['http://localhost:3008']
 app.use(function(req,res,next){
  let origin = req.headers.origin;

  if(whiteList.includes(origin)){
   res.setHeader("Access-Control-Allow-Origin",origin);
   res.setHeader("Access-Control-Allow-Headers","name,years,gender");
   res.setHeader("Access-Control-Allow-Methods","PUT");
   res.setHeader("Access-Control-Max-Age",6);//以秒为单位,6s之内不会再发预检请求
   res.setHeader("Access-Control-Allow-Credentials",true);
   if(req.method === 'OPTIONS'){
    res.end();
   }
  }
  next();
 })

 app.put("/getData",function(req,res){
 })


 app.get("/getData",function(req,res){
 })

 app.use(express.static(__dirname));//将当前目录作为静态目录

 app.listen(4008,() => {
  console.log("have listened on port 4008!")
 })

实现跨域的9种方法