前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >浏览器中的跨域问题与 CORS

浏览器中的跨域问题与 CORS

作者头像
夜尽天明
发布于 2020-09-08 08:14:50
发布于 2020-09-08 08:14:50
1.5K00
代码可运行
举报
文章被收录于专栏:全栈修炼全栈修炼
运行总次数:0
代码可运行

❝Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ❞

❝什么是跨域?[1] ❞

跨域,这或许是前端面试中最常碰到的问题了,大概因为跨域问题是浏览器环境中的特有问题,而且随处可见,如同蚊子不仅盯你肉而且处处围着你转让你心烦。「你看,在服务器发起 HTTP 请求就不会有跨域问题的」

当谈到跨域问题的解决方案时,最流行也最简单的当属 CORS 了。

CORS

CORS 即跨域资源共享 (Cross-Origin Resource Sharing, CORS)。简而言之,就是在服务器端的响应中加入几个标头,使得浏览器能够跨域访问资源。

这个响应头的字段设置就是 Access-Control-Allow-Origin: *

以下是最简单的一个 CORS 请求

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
GET / HTTP/1.1
Host: shanyue.tech
Origin: http://shanyue.tech
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Date: Wed, 08 Jul 2020 17:03:44 GMT
Connection: keep-alive

预请求与 Options

当一个请求跨域且不是简单请求时就会发起预请求,也就是 Options。如果没有预请求,万一有一个毁灭性的 POST 跨域请求直接执行,虽然最后告知浏览器你没有跨域权限,但是损失已造成,岂不亏大的。

以下条件构成了简单请求:

  1. Method: 请求的方法是 GETPOSTHEAD
  2. Header: 请求头是 Content-Type (有限制)、Accept-LanguageContent-Language
  3. Content-Type: 请求类型是 application/x-www-form-urlencodedmultipart/form-datatext/plain

非简单请求一般需要开发者主动构造,在项目中常见的 Content-Type: application/jsonAuthorization: <token> 为典型的「非简单请求」。与之有关的三个字段如下:

  • Access-Control-Allow-Methods: 请求所允许的方法, 「用于预请求 (preflight request) 中」
  • Access-Control-Allow-Headers: 请求所允许的头,「用于预请求 (preflight request) 中」
  • Access-Control-Max-Age: 预请求的缓存时间

写一个 CORS Middleware

既然 CORS 原理如此简单,那就拿起键盘写一个简单的 CORS 中间件吧,CORS 大致是设置几个响应头吧

❝关于 cors 的响应头有哪些?[2] ❞

「关于 CORS 的设置即是对 CORS 相关响应头的设置,因此了解这些 headers 至关重要。无论对于配置的生产者和消费者,及后端和前端而言,都应该掌握!」

以下是关于 CORS 相关的 response headers 及其释义

  • Access-Control-Allow-Origin: 可以把资源共享给那些域名,支持 * 及 特定域名
  • Access-Control-Allow-Credentials: 请求是否可以带 cookie
  • Access-Control-Allow-Methods: 请求所允许的方法, 「用于预请求 (preflight request) 中」
  • Access-Control-Allow-Headers: 请求所允许的头,「用于预请求 (preflight request) 中」
  • Access-Control-Expose-Headers: 那些头可以在响应中列出
  • Access-Control-Max-Age: 预请求的缓存时间

而关于 CORS 的中间件即是使用默认值与配置来设置这些头,如 koa/cors 需要传递以下参数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * CORS middleware
 *
 * @param {Object} [options]
 *  - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
 *  - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH'
 *  - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
 *  - {String|Array} allowHeaders `Access-Control-Allow-Headers`
 *  - {String|Number} maxAge `Access-Control-Max-Age` in seconds
 *  - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`, default is false.
 *  - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown
 * @return {Function} cors middleware
 * @api public
 */

// Example
app.use(cors())

CORS 如何设置多域名

由上,貌似很简单,只需要服务端设置一下 Access-Control-Allow-Origin 就可以轻松解决问题,但其中的坑有可能比你想象地要多很多!

先说回 Access-Control-Allow-Origin,它所允许的值只有两个

  • *: 所有域名
  • shanyue.tech: 特定域名

此时,新问题来了:

❝CORS 如果需要指定多个域名怎么办[3] ❞

「如果使用 Access-Control-Allow-Origin: *,则所有的请求不能够携带 cookie,因此这种方案被摈弃。

因此这个问题需要写代码来解决,根据请求头中的 Origin 来设置响应头 Access-Control-Allow-Origin

  1. 如果请求头不带有 Origin,证明未跨域,则不作任何处理
  2. 如果请求头带有 Origin,证明跨域,根据 Origin 设置相应的 Access-Control-Allow-Origin: <Origin>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 获取 Origin 请求头
const requestOrigin = ctx.get('Origin');

// 如果没有,则跳过
if (!requestOrigin) {
  return await next();
}

// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)

「但此时会出现一个新的问题:缓存」

CORS 与 Vary: Origin

在讨论与 Vary 关系时,先抛出一个问题:

❝如何避免 CDN 为 PC 端缓存移动端页面[4] ❞

假设有两个域名访问 static.shanyue.tech 的跨域资源

  1. foo.shanyue.tech,响应头中返回 Access-Control-Allow-Origin: foo.shanyue.tech
  2. bar.shanyue.tech,响应头中返回 Access-Control-Allow-Origin: bar.shanyue.tech

看起来一切正常,但平静的水面下波涛暗涌:

「如果 static.shanyue.tech 资源被 CDN 缓存,bar.shanyue.tech 再次访问资源时,因缓存问题,因此此时返回的是 Access-Control-Allow-Origin: foo.shanyue.tech,此时会有跨域问题」

此时,Vary: Origin 就上场了,代表为不同的 Origin 缓存不同的资源,这在各个服务器端 CORS 中间件也能体现出来,如以下几段代码

此处是一段 koa 关于 CORS 的处理函数: 详见 koajs/cors[5]

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
return async function cors(ctx, next) {
  // If the Origin header is not present terminate this set of steps.
  // The request is outside the scope of this specification.
  const requestOrigin = ctx.get('Origin');

  // Always set Vary header
  // https://github.com/rs/cors/issues/10
  ctx.vary('Origin');
}

此处是一段 Go 语言关于 CORS 的处理函数: 详见 rs/cors[6]

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
 headers := w.Header()
 origin := r.Header.Get("Origin")

 // Always set Vary, see https://github.com/rs/cors/issues/10
  headers.Add("Vary", "Origin")
}

进一步改进相关代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 获取 Origin 请求头
const requestOrigin = ctx.get('Origin');

// 不管有没有跨域都要设置 Vary: Origin
ctx.set('Vary', 'Origin')

// 如果没有设置,说明没有跨域,跳过
if (!requestOrigin) {
  return await next();
}

// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)

「那此时是不关于 CORS 的问题就解决了?从中间件处理层面是这样的,但仍然有一些服务端中间件使用问题及浏览器问题」

HSTS 与 CORS

HSTS (HTTP Strict Transport Security) 为了避免 HTTP 跳转到 HTTPS 时遭受潜在的中间人攻击,由浏览器本身控制到 HTTPS 的跳转。如同 CORS 一样,它也是有一个服务器的响应头来控制

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Strict-Transport-Security: max-age=5184000

此时浏览器访问该域名时,会使用 307 Internal Redirect,无需服务器干涉,自动跳转到 HTTPS 请求。

「如果前端访问 HTTP 跨域请求,此时浏览器通过 HSTS 跳转到 HTTPS,但浏览器不会给出相应的 CORS 响应头部,就会发生跨域问题。」

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
GET / HTTP/1.1
Host: shanyue.tech
Origin: http://shanyue.tech
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

服务器异常处理与跨域异常

当与其他中间件一起工作时,也有可能出现问题,由于不正确的执行顺序也可能导致跨域失败。

假设有一个参数校验中间件,置于 CORS 中间件上方,由于校验失败,并未穿过 CORS 中间件,在前端会报错跨域失败,真正的参数校验问题掩盖其中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const Koa = require('koa')
const app = new Koa()
const cors = require('@koa/cors')

// 异常处理中间件
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (e) {
    ctx.body = 'hello, error'
  }
})

// 某一个特定时刻肯定会报错的中间件
app.use(async (ctx, next) => {
  throw new Error('hello, world')
})

// CORS 中间件
app.use(cors())

app.listen(3000)

总结

本篇文章介绍了跨域问题及其相应的 CORS 解决方案,并列出了若干细节问题。

  1. CORS 通过服务器端设置若干响应头来正常工作
  2. Access-Control-Allow-Origin: * 无法携带 Cookie,因此以此为多域名跨域设置有缺陷
  3. 服务器端通过响应头 Origin 来判断是否为跨域请求,并以此设置多域名跨域,但要加上 Vary: Origin
  4. 在编码过程中要注意 HSTS 配置及服务器的中间件顺序带来的潜在风险

Reference

[1]

什么是跨域?: https://q.shanyue.tech/fe/js/216.html

[2]

关于 cors 的响应头有哪些?: https://q.shanyue.tech/base/http/328.html

[3]

CORS 如果需要指定多个域名怎么办: https://q.shanyue.tech/base/http/364.html

[4]

如何避免 CDN 为 PC 端缓存移动端页面: https://q.shanyue.tech/base/http/330.html

[5]

koajs/cors: https://github.com/koajs/cors/blob/master/index.js#L54

[6]

rs/cors: https://github.com/rs/cors/blob/be1c7e127af9fce006600894df5c5731d99cdc82/cors.go#L268

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 全栈修炼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
正确配置 CORS:跨域问题解决记录
我们的服务都是基于 k8s 部署,在 ingress 这层都已经设置了允许跨域:
用户4945346
2024/06/17
6930
正确配置 CORS:跨域问题解决记录
Gin CORS 跨域请求资源共享与中间件
请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同,只要协议、域名和端口任意一个不同,都是跨域请求。
贾维斯Echo
2024/01/11
4390
Gin CORS 跨域请求资源共享与中间件
深入理解跨域问题
想了解跨域就要先了解什么是同源策略,就好比你要了解什么苹果手机”越狱“,首先要了解什么是ios操作系统。 了解以下名词:
读懂原理
2022/05/13
1.1K0
深入理解跨域问题
安全系列之:跨域资源共享CORS
简介 什么是跨域资源共享呢? 我们知道一个域是由scheme、domain和port三部分来组成的,这三个部分可以唯一标记一个域,或者一个服务器请求的地址。跨域资源共享的意思就是服务器允许其他的域来访
程序那些事
2021/09/24
3790
【实战晋级】理解跨域以及工作中跨域问题的处理 - 1
相信大部分前端工程师在日常工作中经常使用 xhr 或者 fetch 从后端 api 里取数据然后进行二次处理,随后渲染到页面。
zz_jesse
2020/03/17
5760
Spring Cloud Gateway CORS 方案看这篇就够了
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/04/07
5K0
Spring Cloud Gateway CORS 方案看这篇就够了
Golang——通过实例了解并解决CORS跨域问题
​ 运行在http://localhost:8082端口的前端服务器express和运行在http://localhost:8080端口的后端服务器golang net/http。前端的javaScript代码使用fetch()函数发起一个到http://localhost:8080/api/students的请求。
传说之下的花儿
2023/04/16
2.4K0
Golang——通过实例了解并解决CORS跨域问题
对象存储COS跨域CORS问题小结
CORS(Cross-origin resource sharing) 中文名称"跨域资源共享",由于安全原因,Web 应用程序默认情况只能在同源(协议、域名和端口)的情况下向服务器获取数据。
吴硕卫
2020/11/19
9.5K0
对象存储COS跨域CORS问题小结
CORS解决跨域问题
浏览器中,网站A的网络请求访问网站A的资源(图片,HTTP请求)是很顺畅的,而想访问网站B的资源,就要面对跨域资源访问的问题了。面对跨域问题,有很多的解决方案,本文讨论使用 CORS 来解决的方案。
张云飞Vir
2020/03/27
2K0
【实战晋级】理解跨域以及工作中跨域问题的处理 - 2 预检请求
这个时代每个人的时间都很宝贵,为了不浪费读者的时间,需要读者自测是否需要阅读本文,如果以下问题你都 ok,那你完全可以 break了。
zz_jesse
2020/03/17
6710
ajax cors跨域_jquery跨域
Jsonp 的实现原理就是:创建一个回调函数,然后在远程服务上调用这个函数并且将 JSON 数据形式作为参数传递,完成回调。
全栈程序员站长
2022/09/23
2.7K0
CORS 跨域问题解决办法
我们在编写自己的网站时请求一些接口或者网页资源时,可能会遇到请求无响应的现象,这时按F12查看控制台会发现报出了下面这句错误,这其实就是跨域资源共享(CORS)协议阻止了请求。 Access to XNLAttpRequest at 'https://xxx.xxxx.xxx' from origin 'https://xxx.xxx.xxx' has xxx.xx been blocked by coRs policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
XG.孤梦
2022/09/23
2.3K0
CORS 跨域问题解决办法
CORS原理及@koa/cors源码解析
这是浏览器的同源策略所造成的,同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
木子星兮
2020/07/16
1.2K0
浏览器跨域
完整高频题库仓库地址:https://github.com/hzfe/awesome-interview
HZFEStudio
2021/09/12
3420
跨域问题的一次深入研究
最近在业务代码中深受跨域问题困扰,因此特别写一篇博客来记录一下自己对跨域的理解以及使用到的参考资料。本文的项目背景基于vue+vuex+axios+springboot。涉及以下内容:
眯眯眼的猫头鹰
2018/10/31
1.6K0
跨域和CORS
  同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。    
changxin7
2019/12/19
1.1K0
一篇文章让你搞懂如何通过Nginx来解决跨域问题
出于安全考虑(比如csrf攻击),浏览器一般会禁止进行跨域访问,但是因为有时有相应需求,需要允许跨域访问,这时,我们就需要将跨域访问限制打开。   启动一个web服务,端口是8081
用户4919348
2020/06/21
39.5K2
跨域问题及CORS解决跨域问题方法
跨域不一定会有跨域问题。因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。
Java架构师必看
2021/03/22
12.9K0
Nginx 轻松搞定跨域问题!
点击关注公众号,Java干货及时送达 来源:酒香逢 地址:www.cnblogs.com/fnz0/p/15803011.html 当你遇到跨域问题,不要立刻就选择复制去尝试,请详细看完这篇文章再处理,我相信它能帮到你。 分析前准备: 前端网站地址:http://localhost:8080 服务端网址:http://localhost:59200 首先保证服务端是没有处理跨域的,其次,先用postman测试服务端接口是正常的 当网站8080去访问服务端接口时,就产生了跨域问题,那么如何解决?接下来我
Java技术栈
2022/03/28
5.3K0
你不知道的CORS跨域资源共享
了解下同源策略 源(origin)*:就是协议、域名和端口号; 同源: 就是源相同,即协议、域名和端口完全相同; 同源策略:同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源; 同源策略的分类: DOM 同源策略:即针对于DOM,禁止对不同源页面的DOM进行操作;如不同域名的 iframe 是限制互相访问。 XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。 不受同源策略限制: 页面中的链接,
keyWords
2019/03/20
8810
你不知道的CORS跨域资源共享
相关推荐
正确配置 CORS:跨域问题解决记录
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验