其实跨域问题是后端来解决的? CORS
浏览器的同源策略
浏览器 出于安全 考虑,对同源 请求放行 ,对异源 请求限制 ,这些限制规则统称为同源策略 。因此限制造成的开发问题,称之为跨域问题。
同源的核心判定标准
同源的判定有且仅有三个必须同时完全匹配的核心要素,缺一不可。只要有一个要素不同,就会被判定为「跨域」。
源的标准格式:
协议://域名:端口,路径、查询参数、哈希值不影响同源判定。
| 核心要素 | 判定规则 | 常见注意点 |
|---|---|---|
| 协议(Protocol) | 必须完全一致 | http 与 https、wss 与 ws,均视为不同协议,直接判定跨域 |
| 域名(Domain) | 必须精确匹配 | 根域名与子域名、域名与对应 IP、不同二级域名,均视为不同域名 |
| 端口(Port) | 必须完全一致 | 省略端口时,使用协议默认端口(http 默认 80,https 默认 443),默认端口与显式端口不一致也判定跨域 |
同源判定示例(基准源:http://www.example.com/index.html)
| 待检测 URL | 是否同源 | 核心原因 |
|---|---|---|
http://www.example.com/user/info.html |
✅ | 协议、域名、端口完全一致,仅路径不同不影响 |
https://www.example.com |
❌ | 协议不同(http→https) |
http://api.example.com |
❌ | 域名不同(二级域名不一致) |
http://www.example.com:8080 |
❌ | 端口不同(默认 80→8080) |
http://example.com |
❌ | 域名不同(根域名与 www 二级域名不匹配) |
http://192.168.1.100 |
❌ | 域名与对应 IP 不匹配,即使解析结果一致也不算同源 |
同源策略的核心限制范围
同源策略的限制,主要集中在数据存储、DOM 访问、网络请求三大核心维度,这也是绝大多数跨域问题的根源。
数据存储的强隔离
不同源的站点之间,无法互相读取 / 写入浏览器端的敏感存储数据,包括:
- Cookie、SessionStorage、LocalStorage
- IndexedDB、Web SQL 数据库
- 加密的媒体资源、缓存文件
例外:Cookie 可通过
domain属性放宽至同根域名下的子域名(如设置domain=example.com,则a.example.com和b.example.com可共享该 Cookie),同时受SameSite、Secure属性的额外约束。
DOM 节点的访问限制
不同源的页面之间,无法互相操作 DOM 和 JavaScript 对象,典型场景:
- 父页面无法读取 / 修改
<iframe>嵌入的跨域页面的 DOM - 跨域 iframe 无法操作父页面的 DOM 和全局 JS 变量
window.open打开的跨域新窗口,与原窗口之间无法互相访问 DOM
网络请求的拦截(最常见跨域问题)
浏览器默认禁止前端 JS 通过XMLHttpRequest、fetch等 AJAX 方式,发起跨域请求并读取响应内容。
跨域不是请求发不出去,而是浏览器拦截了响应。绝大多数场景下,请求已正常发送到后端并得到响应,只是浏览器发现不符合同源规则、且无合法跨域授权时,会阻止 JS 读取响应数据,并在控制台抛出跨域错误。
同源策略不限制的行为
同源策略并非一刀切禁止所有跨源交互,以下场景不受限制:
- 带有
src/href属性的 HTML 标签资源 加载:<script>、<img>、<link>、<video>、``、<iframe>等,浏览器允许加载跨域资源;但会限制 JS 对加载后内容的读取(如跨域图片无法通过 canvas 读取像素、跨域脚本的报错信息会被脱敏)。 - 表单的跨域提交:form 表单可直接提交到跨域地址,但提交后原页面无法读取响应内容。
- 超链接跳转、
window.open打开新窗口:可正常跳转跨域地址,但无法操作新窗口的 DOM 和存储数据。 - WebSocket 协议:本身不受同源策略限制,可自由跨域通信。
CORS跨域请求
后端通过在 HTTP 响应头中添加明确的授权标识,告诉浏览器 "这个跨域请求是被允许的",浏览器识别后就会放行响应数据。
CORS 的两种请求类型
浏览器将 CORS 请求分为简单请求 和非简单请求,两者的处理流程不同。
简单请求(直接发送,无预检)
同时满足以下两个条件的请求为简单请求:
-
请求方法 :仅
GET、POST、HEAD之一 -
请求头:仅包含以下安全头(无自定义头):
AcceptAccept-LanguageContent-LanguageContent-Type(仅允许text/plain、multipart/form-data、application/x-www-form-urlencoded)
流程:浏览器直接发送请求,后端返回响应并带上 CORS 头,浏览器检查头后决定是否放行响应。
非简单请求(先预检,再发送)
不满足简单请求条件的均为非简单请求(如 PUT/DELETE 请求、带自定义头的请求、Content-Type: application/json 的请求)。
流程:
- 预检请求(OPTIONS) :浏览器先自动发送一个
OPTIONS方法的请求,询问后端 "是否允许该跨域请求" - 后端响应预检 :后端返回
204 No Content状态码,并带上 CORS 授权头 - 正式请求:浏览器收到预检通过的响应后,再发送真正的业务请求
- 返回响应:后端处理业务并返回数据
预检请求的作用:在发送可能修改服务器数据的请求前,先确认后端是否允许,避免对服务器造成意外影响。
go
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
// 核心三行CORS头
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 处理OPTIONS预检
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
-
Content-Type:前端发 JSON 数据时(比如axios.post传对象),会自动带这个头,值是application/json。如果不允许,后端收不到 JSON 数据。 -
Authorization:前端登录后传 Token 时(比如Bearer xxx),会带这个头。如果不允许,后端拿不到 Token,验证不了身份。