什么是CORS?
CORS即“跨域资源共享”,这是一种最常用的跨域实现方式,一般需要后端人员在处理请求数据的时候,添加允许跨域的相关请求头信息。大致思路是这样的:首先获取请求对象的信息,比如Origin字段,通过预先配置的参数判断请求是否合法,然后设置相应对象response的头信息,实现跨域资源请求。
响应头设置
- Access-Control-Allow-Origin
- 含义:允许哪个源可以访问
- 实际操作:
- 后端一般会设置一个白名单
- 当前端发请求到后端的时候,后端会判断是否在白名单里,如果在,则在响应头里设置该头:
res.setHeader('Access-Control-Allow-Origin',前端的域);
- Access-Control-Allow-Headers
- 含义:允许携带哪个头访问,即允许前端设置的自定义头字段传递数据
- 实际操作:
- 前端需要通过头信息带一个字段和值给后端,比如在requestHeader中带name为yuhua给后端
- 则前端ajax可以如下操作:
xhr.setRequestHeader('name','yuhua');//xhr为ajax对象,通过new XMLHttpRequest得到
- 当前端发送请求给后端的时候,后端需要在响应头里设置该头:
res.setHeader('Access-Control-Allow-Headers','name')
- 如果值设置为* ,那么再写凭证的头(Access-Control-Allow-Credentials)就会报错
- Access-Control-Allow-Methods、Access-Control-Max-Age
- 含义:前者:允许携带哪个方法访问,即允许所设置的请求方式请求;后者:允许预检存货时间
- 实际操作:
- 假设前端想要通过PUT请求
- 当前端发请求到后端的时候,后端一般会设置允许的请求方式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预检请求
- 请求方式只能是GET、POST、HEAD
- HTTP请求头限制这几种字段:Accept、Accept-Languag、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width
- 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则可以,其他值均不可以。
- 实际操作:
- 举例前端需要带cookie到后端,比如前端设置了如下cookie
document.cookie = 'name=yuhua';
- 那么,前端如果直接发请求给后端,即便前端带上了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的请求头里的字段及值
-
实际操作:
- 假设后端向对前端返回一个自定义头name,值为yuhua,可以如下操作
res.setHeader('name','yuhua');
- 但是这种方式有问题,前端会报unsafe错误。那么该如何设置才能正确呢?应该再后端加上Access-Control-Expose-Headers头
res.setHeader("Access-Control-Expose-Headers","name");
- 这样,再res.setHeader就可以了,前端可以通过以下字段拿到:
xhr.getResponseHeader("name");
如何得到后端返回的头信息?
- 后端设置两点
- 设置Access-Control-Expose-Headers
res.setHeader("Access-Control-Expose-Headers","name");
- 设置返回头的值
res.setHeader('name','yuhua');
- 前端用如下方式得到:
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种方法
- jsonp
- cors
- postMessage
- document.domain
- window.name
- location.hash
- http-proxy 后续会有详细文章阐述
- nginx 后续会有详细版块阐述
- websocket