一个案例教你使用“跨域资源共享”

跨域资源共享(CORS),是一种解决跨域问题的解决方案。它的实现需要浏览器、服务器同时支持。又因为现代浏览器都已经支持了这个机制,所以CORS的实现关键在于服务器。

我们将根据"发送请求前,是否会发送预检请求"这一特征来将请求分为2类,一类是简单请求,一类是非简单请求。

下面我们将逐个进行讲解。

简单请求

简单请求,直接就将请求发送给服务器。简单请求具有以下特征:

  • 请求方式必须是 GETHEADPOST 这里面的其中一个。
  • Content-Type字段的值必须是 text/plainmultipart/form-dataapplication/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;另一类是自己实现。

我们这里选择自己实现。代码如下:

javascript 复制代码
router.get('/req1', function(req, res, next) {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.send('响应成功-1');
});

这个时候我们再来看一下效果:

什么场景下,会用到简单请求呢?从业务上来看,跟用户没关系的功能一般都是简单请求。一般都是GET请求去获取一些整体资源啥的。

非简单请求

发送真正的请求前,会自动发送一个预检请求,这个预检请求是用来询问服务器,是否支持CORS。

除了简单请求,剩下的都是非简单请求。

服务端如何支持?

想发送一个非简单请求还是比较简单的,主要体现在额外的请求头字段、Content-Type的值为其他类型即可。

前端可能会这么来写:

javascript 复制代码
let result = await axios.create({
    baseURL: 'http://127.0.0.1:8888/',
    headers: {
        user_token: '123-qwe-789-gpto901'
    }
})
.post(
    '/cors/req2',
    {
        name: '章三',
    }
);

后端可能会这么写:

javascript 复制代码
router.post('/req2', function(req, res, next) {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.send('响应成功-1');
});

如果此时发送请求,那它一定会有跨域问题。

这个报错信息也是很全面的,如何改正,它已经暗示我们了:

请求头"Access-Control-Allow-Origin"字段没有被预设在对应的请求资源上。

最开始的时候,就有说过,非简单请求在真正发送请求前,会发送一个options的预检请求。

所以我们需要单独给这个接口加上options方法的处理,处理如下:

javascript 复制代码
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"字段,后端接口修改如下:

javascript 复制代码
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('');
});

此时再次点击按钮,发现还是报错,信息如下:

说我们响应头字段里还少东西,那就接着添加呗:

javascript 复制代码
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。

现在来动手实践一下。

前端做出修改如下:

javascript 复制代码
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: '章三',
    }
);

后端修改如下:

javascript 复制代码
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 字段,这个字段用来判断 资源是否缓存失效的一个指标,后端做出修改如下:

javascript 复制代码
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字段就能够获取到了。

最后

好啦,本期分享到这里就结束啦,希望我的分享能够对你有帮助,我们下期再见啦,拜拜~~

关注公众号:熬夜学前端,获取更多干货实践,欢迎交流分享。

相关推荐
LCG元38 分钟前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
迷雾漫步者40 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css