跨域资源共享(CORS),是一种解决跨域问题的解决方案
。它的实现需要浏览器、服务器同时支持。又因为现代浏览器都已经支持了这个机制,所以CORS的实现关键在于服务器。
我们将根据“发送请求前,是否会发送预检请求”
这一特征来将请求分为2类,一类是简单请求,一类是非简单请求。
下面我们将逐个进行讲解。
简单请求
简单请求,直接就将请求发送给服务器。简单请求具有以下特征:
- 请求方式必须是
GET
、HEAD
、POST
这里面的其中一个。 - Content-Type字段的值必须是
text/plain
、multipart/form-data
、application/x-www-form-urlencoded
这里面的其中一个。 - 允许你新增请求头字段,但是新增的请求头字段 必须是下面集合里的某一个。
1、Accept
2、Accept-Language
3、Content-Language
4、Content-Type
5、Range
服务端如何支持?
特别提醒:
上方的图片便是未支持CORS而爆出来的错误,错误信息很详细,大家在工作中,根据错误提示一步一步修改即可。
针对简单请求,想要让服务端支持CORS,2种方式,分别如下:
- 将
Access-Control-Allow-Origin
字段的值设置为 “*”。 - 将
Access-Control-Allow-Origin
字段的值设置为“非*”。
这2种方式的区别在于请求是否可以携带cookie。
第一种方式,想要携带用户信息,只能通过新增请求头字段的方式来携带。
第二种方式,想要携带用户信息,除了可以使用第一种方式,还可以使用cookie。
在这篇文章里,我们使用express来搭建服务器,express想要支持CORS,2种手段,一类是使用第三方库,比如cors
;另一类是自己实现。
我们这里选择自己实现。代码如下:
router.get('/req1', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.send('响应成功-1');
});
这个时候我们再来看一下效果:
什么场景下,会用到简单请求呢?从业务上来看,跟用户没关系的功能一般都是简单请求。一般都是GET请求去获取一些整体资源啥的。
非简单请求
发送真正的请求前,会自动发送一个预检请求,这个预检请求是用来询问服务器,是否支持CORS。
除了简单请求,剩下的都是非简单请求。
服务端如何支持?
想发送一个非简单请求还是比较简单的,主要体现在额外的请求头字段、Content-Type的值为其他类型即可。
前端可能会这么来写:
let result = await axios.create({
baseURL: 'http://127.0.0.1:8888/',
headers: {
user_token: '123-qwe-789-gpto901'
}
})
.post(
'/cors/req2',
{
name: '章三',
}
);
后端可能会这么写:
router.post('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.send('响应成功-1');
});
如果此时发送请求,那它一定会有跨域问题。
这个报错信息也是很全面的,如何改正,它已经暗示我们了:
请求头“Access-Control-Allow-Origin”字段没有被预设在对应的请求资源上。
最开始的时候,就有说过,非简单请求在真正发送请求前,会发送一个options的预检请求。
所以我们需要单独给这个接口加上options方法的处理,处理如下:
router.post('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.send('响应成功-1');
});
router.options('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.send('');
});
此时再发送以下请求,接口还是报错,报错信息如下:
那就再接着修改呗,这次提示我们说要在“Access-Control-Allow-Headers”里添加“content-type”字段,后端接口修改如下:
router.post('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "content-type");
res.send('响应成功-1');
});
router.options('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "content-type");
res.send('');
});
此时再次点击按钮,发现还是报错,信息如下:
说我们响应头字段里还少东西,那就接着添加呗:
router.post('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "user_token,content-type");
res.send('响应成功-1');
});
router.options('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "user_token,content-type");
res.send('');
});
此时再次点击按钮,这时我们发现,这个非简单请求的CORS终于成功了。
总结如下:
想要让服务器支持非简单请求的CORS,需要做出如下设置:
- options的请求、真正的请求都需要设置 “Access-Control-Allow-Origin”响应头字段。
- options的请求、真正的请求都需要设置 “Access-Control-Allow-Headers”响应头字段。
CORS过程中,请求如何携带cookie?
假如我们有这样的一个cookie:
我们想要去携带Cookie,需要做出如下修改:
- 前端需要将 withCredentials 需要设置为true。
- 后端需要将 “Access-Control-Allow-Origin” 设置为非*号。
- 后端还需要将 “Access-Control-Allow-Credentials”设置为true。
现在来动手实践一下。
前端做出修改如下:
let result = await axios.create({
baseURL: 'http://127.0.0.1:8888/',
headers: {
user_token: '123-qwe-789-gpto901'
},
withCredentials: true
})
.post(
'/cors/req2',
{
name: '章三',
}
);
后端修改如下:
router.post('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:3000");
res.setHeader("Access-Control-Allow-Headers", "user_token,content-type");
res.setHeader("Access-Control-Allow-Credentials", true);
res.send('响应成功-1');
});
router.options('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:3000");
res.setHeader("Access-Control-Allow-Headers", "user_token,content-type");
res.setHeader("Access-Control-Allow-Credentials", true);
res.send('');
});
点击发送请求后,我们发现,请求自动携带了cookie。
CORS过程中,如何设置OPTIONS预检请求的缓存时长?
这个就直接说结论了,篇幅有些过长,就不跟着写代码了,想要实践的小伙伴可以自行实践。
设置 Access-Control-Max-Age
字段就可以了,这个字段的单位是 “秒”。
CORS过程中,前端如何拿到其他的请求头字段?
不知道大家有没有关注过一个现象,我们一般都会用axios来进行通信。但是,在响应拦截器里,我们会发现,这里的头字段并不总是全的,如下:
我们真实的响应头字段却有很多个,如下:
针对这一个现象,如果前端想要获取到额外的响应头字段,后端就需要设置Access-Control-Expose-Headers响应头字段
。
就比如,前端想要在拦截器里获取 Etag
字段,这个字段用来判断 资源是否缓存失效的一个指标,后端做出修改如下:
router.post('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:3000");
res.setHeader("Access-Control-Allow-Headers", "user_token,content-type");
res.setHeader("Access-Control-Allow-Credentials", true);
res.setHeader("Access-Control-Expose-Headers", "X-Powered-By,Etag");
res.send('响应成功-1');
});
router.options('/req2', function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:3000");
res.setHeader("Access-Control-Allow-Headers", "user_token,content-type");
res.setHeader("Access-Control-Allow-Credentials", true);
res.setHeader("Access-Control-Expose-Headers", "X-Powered-By,Etag");
res.send('');
});
此时我们再console一下响应拦截器,我们会发现,Etag字段就能够获取到了。
最后
好啦,本期分享到这里就结束啦,希望我的分享能够对你有帮助,我们下期再见啦,拜拜~~
关注公众号:熬夜学前端,获取更多干货实践,欢迎交流分享。
原文链接:https://juejin.cn/post/7334882866012110883 作者:小九九的爸爸