浏览器跨域

1. 核心概念

1.1 什么是跨域

你一定见过这个报错:

1
2
Access to XMLHttpRequest at 'https://api.com/data'
from origin 'https://app.com' has been blocked by CORS policy

Postman 能调通,浏览器就不行——问题不在接口,而在浏览器的同源策略(Same-Origin Policy, SOP)。

跨域指的是:网页脚本试图访问与自身协议(scheme)、域名(host)、端口(port)任一不同的资源时,浏览器对这次访问施加的限制。

请求已经发出去了,服务端也已经处理并返回了响应。浏览器拦截的是响应——它不让 JS 读取返回的数据。

类比理解:你住在封闭小区,保安(浏览器)规定只有本小区(同源)的快递可以签收。外面的快递员(跨域请求)想送货进来,必须由物业(服务端)提前开好通行证(CORS 响应头),保安验证通过才放行。

1.2 同源的精确判定

同源要求协议、域名、端口三者完全一致。不是 “ 差不多 “,而是完全一致。

https://www.example.com:443/path 为基准:

比较 URL是否同源原因
https://www.example.com:443/other✅ 同源路径不同不影响
http://www.example.com:443❌ 跨域协议不同(http vs https)
https://www.example.com:8080❌ 跨域端口不同(443 vs 8080)
https://api.example.com:443❌ 跨域域名不同(子域名也算不同)
https://example.com:443❌ 跨域域名不同(www vs 裸域)
https://www.example.com✅ 同源HTTPS 默认端口就是 443

常见误区:http://a.com 请求 https://a.com,很多人觉得 “ 域名一样就没事 “。但协议不同(http vs https),端口也不同(80 vs 443),这是跨域。

1.3 同源策略限制了什么

同源策略并非什么都拦,它有明确的限制范围:

行为是否受限说明
<img src="..."> 加载图片❌ 不受限但 JS 无法读取 Canvas 中跨域图片的像素数据
<script src="..."> 加载脚本❌ 不受限JSONP 正是利用了这一点
<link> 加载 CSS❌ 不受限
<iframe> 嵌入页面⚠️ 部分受限可以嵌入,但 JS 无法访问跨域 iframe 的 DOM
XMLHttpRequest / fetch✅ 受限跨域问题最主要的触发场景
@font-face 加载字体✅ 受限跨域字体必须有 CORS 头
localStorage / Cookie 读取✅ 受限只能访问同源的存储

1.4 核心术语速查

术语一句话解释类比
Same-Origin Policy (SOP)协议 + 域名 + 端口必须一致同一小区住户
CORS服务端声明 “ 谁有权限访问我 “物业开通行证
Preflight Request复杂请求前的 OPTIONS 试探先打电话确认能不能送
Origin请求头中标识来源的字段快递单上的寄件地址
Access-Control-Allow-Origin服务端声明允许的来源通行证上写的小区名
Access-Control-Max-Age预检结果的缓存时间通行证的有效期
Simple Request满足特定条件、无需预检的请求挂号信可以直接投递
CORB浏览器阻止跨域响应进入渲染进程保安直接把可疑包裹退回

理解了这些基础概念,接下来看浏览器内部到底怎么处理一次跨域请求。

2. 浏览器内部处理流程

2.1 从 fetch() 到网络层:一次跨域请求的完整旅程

在 JS 中调用 fetch('https://api.com/data') 时,浏览器内部经历以下流程:

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3B82F6', 'actorTextColor': '#1E3A5F', 'actorBorder': '#2563EB', 'signalColor': '#60A5FA', 'activationBkgColor': '#DBEAFE', 'activationBorderColor': '#3B82F6'}}}%%
sequenceDiagram
    autonumber
    participant JS as "JS 引擎 (V8)"
    participant Blink as "渲染引擎 (Blink)"
    participant NP as "网络进程 (Network Service)"
    participant Server as "目标服务器"

    JS->>Blink: "fetch('https://api.com/data')"
    activate Blink
    Blink->>Blink: "检查 Origin 与目标是否同源"
    Note over Blink: "不同源 → 进入 CORS 流程"
    Blink->>Blink: "判断是否简单请求"

    alt 需要预检
        Blink->>NP: "发送 OPTIONS 预检请求"
        activate NP
        NP->>Server: "OPTIONS /data"
        Server-->>NP: "204 + CORS 响应头"
        NP-->>Blink: "预检响应"
        deactivate NP
        Note over Blink: "校验 CORS 头是否允许"
    end

    Blink->>NP: "发送正式请求 + Origin 头"
    activate NP
    NP->>Server: "GET /data + Origin: https://app.com"
    Server-->>NP: "200 + 数据 + CORS 头"
    NP-->>Blink: "响应数据"
    deactivate NP

    Blink->>Blink: "校验 Access-Control-Allow-Origin"

    alt 校验通过
        Blink-->>JS: "返回 Response 对象"
    else 校验失败
        Blink-->>JS: "抛出 TypeError,响应体置空"
    end
    deactivate Blink

三个关键点:

  • CORS 校验在渲染进程(Blink)完成,不在网络层
  • 网络进程只负责收发数据,不做 CORS 判断
  • 服务端已经收到请求并处理完毕,校验失败只是浏览器不把响应交给 JS

2.2 简单请求的判定条件

浏览器将同时满足以下全部条件的请求视为 “ 简单请求 “,无需发送预检:

五个条件:

  1. 方法限于 GETHEADPOST
  2. 请求头仅包含安全字段:AcceptAccept-LanguageContent-LanguageContent-Type(值有额外限制)、Range(仅简单 range 值)
  3. Content-Type 仅限 application/x-www-form-urlencodedmultipart/form-datatext/plain
  4. 请求中没有使用 ReadableStream 对象
  5. XMLHttpRequest 对象没有注册 upload 事件监听

实际开发中,只要用了 Content-Type: application/json 或自定义 Header(如 Authorization),就一定是非简单请求,一定会触发预检。

2.3 SOP、CORS、CORB 的区别

这三个概念容易混淆,用一张表理清:

维度SOP(同源策略)CORSCORB
定义浏览器的基础安全模型放宽 SOP 的标准机制阻止跨域响应进入渲染进程
实现层渲染引擎 (Blink)渲染引擎 (Blink)网络进程 (Network Service)
作用时机JS 尝试跨域操作时收到跨域响应后响应到达渲染进程之前
保护目标防止 JS 读取跨域数据允许授权的跨域访问防止 Spectre 等侧信道攻击
核心逻辑拒绝一切跨域 DOM / 数据访问检查 Access-Control-*按 MIME 类型拦截(HTML/JSON/XML)
失败表现JS 报错fetch 返回 opaque response响应体被清空,Network 面板显示正常

CORB 是针对 Spectre 漏洞的额外防线。即使没有 CORS 头,<img src="api/secret.json"> 这种请求虽然会发出,但 CORB 会在网络层直接清空 JSON 响应体,防止恶意脚本通过侧信道嗅探内存中的数据。

理解了浏览器内部机制,接下来看生产环境中如何正确配置跨域。

3. 生产环境最佳实践

3.1 Nginx 反向代理(推荐方案)

在生产环境中,最干净的做法是通过 Nginx 将前端和后端统一到同一域名下,从架构层面消灭跨域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 443 ssl;
server_name app.example.com;

# 前端静态文件
location / {
root /var/www/frontend/dist;
try_files $uri $uri/ /index.html;
}

# 后端 API 反向代理 —— 同域,无跨域
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

前端请求 https://app.example.com/api/users,Nginx 转发到后端 http://127.0.0.1:8080/users。浏览器看到的始终是同域请求,跨域问题根本不存在。

如果前后端必须分域,在 Nginx 中统一添加 CORS 头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location /api/ {
# CORS 头 —— 统一在网关层处理,后端无需关心
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' '7200' always;

# 处理预检请求 —— 直接返回 204,不转发到后端
if ($request_method = 'OPTIONS') {
return 204;
}

proxy_pass http://127.0.0.1:8080/;
}

always 关键字很重要:默认 add_header 只在 2xx/3xx 响应中生效。加了 always,即使后端返回 4xx/5xx 也会带上 CORS 头,避免 “ 正常请求有 CORS 头、报错时没有 “ 的坑。

3.2 Go Gin CORS 中间件

3.2.1 基础配置

1
2
3
4
5
6
7
8
9
10
import "github.com/gin-contrib/cors"

engine.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://app.example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"X-Request-Id"},
AllowCredentials: true,
MaxAge: 2 * time.Hour,
}))

各配置项的含义:

配置项作用对应响应头
AllowOrigins允许哪些域名访问Access-Control-Allow-Origin
AllowMethods允许哪些 HTTP 方法Access-Control-Allow-Methods
AllowHeaders允许前端发送哪些请求头Access-Control-Allow-Headers
ExposeHeaders允许前端 JS 读取哪些响应头Access-Control-Expose-Headers
AllowCredentials是否允许携带 CookieAccess-Control-Allow-Credentials
MaxAge预检结果缓存时间Access-Control-Max-Age

3.2.2 多域名动态匹配

当有多个前端域名时,不能用 *(因为 AllowCredentials: true),可以用 AllowOriginFunc 动态判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
engine.Use(cors.New(cors.Config{
AllowOriginFunc: func(origin string) bool {
allowed := map[string]bool{
"https://app.example.com": true,
"https://admin.example.com": true,
}
// 开发环境放行 localhost
if strings.HasPrefix(origin, "http://localhost") {
return true
}
return allowed[origin]
},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 2 * time.Hour,
}))

3.2.3 ExposeHeaders 详解

默认情况下,跨域响应中前端 JS 只能读取 7 个安全响应头:Cache-ControlContent-LanguageContent-LengthContent-TypeExpiresLast-ModifiedPragma

其他响应头(即使存在于响应中)前端 JS 一律读不到:

1
2
const res = await fetch('https://api.com/data');
res.headers.get('X-Request-Id'); // null(读不到!)

只有通过 ExposeHeaders 声明后才可读取。如果前端无需读取自定义响应头,此项留空即可。

3.2.4 AllowOrigins: ["*"] + AllowCredentials: true 的陷阱

CORS 规范明确规定:当 Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin 不能是 *

Gin 的 cors 中间件内部做了兼容处理——将 * 自动替换为请求中的 Origin 值。但这等同于信任所有来源,存在安全风险。生产环境务必指定具体域名或使用 AllowOriginFunc

3.3 开发环境代理方案

3.3.1 Vite

1
2
3
4
5
6
7
8
9
10
11
12
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true, // 将 Host 设为目标地址
rewrite: (path) => path.replace(/^\/api/, ''),
}
}
}
}

前端请求 /api/users → Vite 开发服务器转发到 https://api.example.com/users。浏览器看到的是同域请求,跨域被绕过。

3.3.2 Webpack (Create React App)

1
2
3
4
5
6
7
8
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use('/api', createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true,
}));
};

代理方案的本质:浏览器 → 同域的开发服务器 → 转发到跨域的后端。服务端之间不存在跨域限制,所以问题被绕过了。

跨域请求默认不携带 Cookie。要携带需要前后端同时配置:

前端:

1
2
3
4
5
// fetch
fetch('https://api.com/data', { credentials: 'include' });

// axios
axios.defaults.withCredentials = true;

后端(必须同时满足):

1
2
Access-Control-Allow-Origin: https://app.com   # 不能是 *
Access-Control-Allow-Credentials: true

还需关注 Cookie 的 SameSite 属性:

SameSite 值跨域请求是否携带 Cookie说明
None✅ 携带必须同时设置 Secure(仅 HTTPS)
Lax(浏览器默认值)⚠️ 部分携带仅顶级导航的 GET 请求携带
Strict❌ 不携带任何跨站请求都不带

Chrome 80+ 开始默认 SameSite=Lax。如果跨域接口依赖 Cookie,必须显式将 Cookie 设为 SameSite=None; Secure

4. 跨域解决方案

4.1 CORS(标准方案,首选)

CORS(Cross-Origin Resource Sharing)是 W3C 标准,通过服务端在响应头中声明权限来实现跨域访问控制。

完整 CORS 响应头一览:

响应头作用示例值
Access-Control-Allow-Origin允许的来源https://app.com*
Access-Control-Allow-Methods允许的 HTTP 方法GET, POST, PUT, DELETE
Access-Control-Allow-Headers允许的请求头Authorization, Content-Type
Access-Control-Expose-Headers允许前端 JS 读取的响应头X-Request-Id, X-Total-Count
Access-Control-Allow-Credentials是否允许携带凭证true
Access-Control-Max-Age预检缓存秒数7200

4.2 反向代理

原理:让浏览器只跟同域的代理服务器通信,由代理转发请求到真实后端。服务端之间不存在跨域限制。

1
2
3
浏览器 → https://app.com/api/users(同域,无跨域)
↓ Nginx 转发
https://api-internal.com:8080/users(浏览器不可见)

适用场景:生产环境同域部署、开发环境 Vite/Webpack proxy。

4.3 postMessage(跨窗口通信)

当两个不同源的页面需要通信时(如主页面与 iframe、窗口与弹出窗口),使用 window.postMessage

1
2
3
4
5
6
7
8
9
10
11
12
// 发送方(https://app.com)
const iframe = document.getElementById('child');
iframe.contentWindow.postMessage(
{ type: 'AUTH', token: 'xxx' },
'https://embed.com' // 指定目标 origin,不要用 '*'
);

// 接收方(https://embed.com 的 iframe 内)
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.com') return; // 校验来源
console.log(event.data); // { type: 'AUTH', token: 'xxx' }
});

安全要点:必须校验 event.origin。用 * 作为 targetOrigin 或不校验来源,等于把大门敞开。

4.4 WebSocket

WebSocket 协议不受同源策略限制,握手阶段使用 HTTP,但一旦升级为 WebSocket 连接,浏览器不再执行 CORS 检查。

1
2
// 可以直接连接跨域的 WebSocket 服务
const ws = new WebSocket('wss://realtime.example.com/ws');

但这不意味着 WebSocket 没有安全风险:

  • 服务端应校验 Origin 头,防止恶意网站建立连接
  • 使用 wss://(加密)而非 ws://
  • 连接建立后应自行实现鉴权机制

4.5 跨域方案对比

维度CORS反向代理postMessageWebSocketJSONP
原理服务端设置响应头代理转发到同域窗口间消息传递独立协议,不受 SOPscript 标签无跨域限制
支持方法全部 HTTP 方法全部 HTTP 方法N/A双向通信仅 GET
安全性高,可精细控制中,需校验 origin中,需服务端校验低,XSS 风险
适用场景API 跨域(标准方案)同域部署 / 开发环境iframe / 弹窗通信实时通信已过时,仅兼容旧系统
浏览器兼容IE10+无限制IE8+IE10+全部
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
    A(["需要跨域通信"]) --> B{"通信场景?"}

    B -->|"前端调后端 API"| C{"能否统一域名?"}
    C -->|"可以"| D["反向代理 ✅"]
    C -->|"不行"| E["CORS ✅"]

    B -->|"iframe / 弹窗通信"| F["postMessage ✅"]
    B -->|"实时双向通信"| G["WebSocket ✅"]
    B -->|"兼容远古浏览器"| H["JSONP(不推荐)"]

    classDef start fill:#3B82F6,stroke:#2563EB,color:#fff,stroke-width:2px
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#1E3A5F
    classDef decision fill:#DBEAFE,stroke:#3B82F6,color:#1E3A5F

    class A start
    class D,E,F,G success
    class H warning
    class B,C decision

5. 同源策略的设计演进

5.1 1995:诞生

同源策略由 Netscape Navigator 2.0(1995 年)引入,最初设计者是 Brendan Eich 团队。当时的目标:防止一个页面中的脚本读取另一个页面的内容。

那时 Web 才刚开始有 “ 交互 “ 的概念。Netscape 意识到,如果不加限制,一个恶意页面可以通过 <iframe> 嵌入银行网站,然后用 JS 读取 iframe 中的 DOM——包括账户余额、转账表单。

5.2 从 “ 全部禁止 “ 到 “ 有条件放行 “

%%{init: {'theme': 'base', 'themeVariables': {'cScale0': '#3B82F6', 'cScale1': '#10B981', 'cScale2': '#F59E0B', 'cScale3': '#EF4444'}}}%%
timeline
    title 跨域安全机制演进
    section 1995-2004
        1995 : Netscape 引入 SOP : 奠定 Web 安全基石
        1999 : IE5 引入 XMLHttpRequest : AJAX 出现,跨域需求激增
        2004 : W3C 开始制定 CORS 草案 : 标准化跨域解决方案
    section 2005-2014
        2005-2008 : JSONP 流行 : hack 方案,利用 script 标签不受限
        2014 : CORS 成为 W3C 正式推荐标准 : 取代 JSONP 成为正统方案
    section 2018-2020
        2018 : Chrome 实施 Site Isolation : Spectre 漏洞推动更严格的进程隔离
        2019 : CORB 默认启用 : 在网络层增加一道防线
        2020 : SameSite Cookie 默认 Lax : Chrome 80+ 收紧跨站 Cookie

5.3 为什么是浏览器限制,而非服务端限制

这是面试高频问题。核心逻辑:

  1. 服务端无法区分 “ 用户主动访问 “ 和 “ 恶意脚本自动请求 “——两者的 HTTP 请求在技术上完全一样
  2. 浏览器能区分——浏览器知道发起请求的脚本来自哪个 Origin
  3. 保护的是用户,不是服务端——服务端自己的数据本来就在自己手里,不需要保护。需要保护的是正在浏览器中登录银行的用户,防止恶意脚本代替用户操作

所以 curl、Postman、服务端 HTTP 客户端永远不会遇到跨域——它们不是浏览器,不需要保护 “ 正在使用的用户 “。

6. 安全视角

6.1 如果没有同源策略

假设同源策略不存在,一个攻击场景:

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3B82F6', 'actorTextColor': '#1E3A5F', 'actorBorder': '#2563EB', 'signalColor': '#60A5FA', 'activationBkgColor': '#DBEAFE', 'activationBorderColor': '#3B82F6'}}}%%
sequenceDiagram
    autonumber
    participant User as "用户"
    participant Evil as "evil.com"
    participant Bank as "bank.com"

    User->>Bank: "登录银行,获得 Session Cookie"
    User->>Evil: "点击恶意链接,打开 evil.com"
    Evil->>Evil: "执行恶意 JS 代码"
    Evil->>Bank: "fetch('/api/balance', {credentials: 'include'})"
    Note over Evil,Bank: "浏览器自动带上 bank.com 的 Cookie"
    Bank-->>Evil: "返回余额数据(无 SOP 时可读取)"
    Evil->>Evil: "窃取用户余额、交易记录"
    Evil->>Bank: "发起转账请求"
    Note over Evil: "用户资产被盗"

有了同源策略,第 6 步被阻断——浏览器不让 evil.com 的 JS 读取 bank.com 的响应。

6.2 CSRF:同源策略挡不住的攻击

同源策略只阻止读取响应,不阻止发送请求。CSRF(Cross-Site Request Forgery)利用了这一点——攻击者不需要读取响应,只需要让请求发出去就够了。

比如一个简单的 <img src="https://bank.com/api/transfer?to=hacker&amount=10000">,浏览器会自动发送这个 GET 请求并带上 Cookie。服务端以为是用户操作,就执行了转账。

防御 CSRF 的手段:

  • CSRF Token:服务端生成随机 Token,表单提交时校验
  • SameSite Cookie:设为 StrictLax,阻止跨站自动携带
  • 检查 Origin / Referer 头:服务端验证请求来源

6.3 XSS:让同源策略形同虚设

如果攻击者通过 XSS(Cross-Site Scripting)在目标页面注入了恶意脚本,那么这段脚本已经是同源的了——同源策略完全不起作用。

1
2
3
存储型 XSS:攻击者在 bank.com 的评论区注入 <script>,
所有访问评论页的用户都会执行恶意代码。
这些代码和 bank.com 同源,可以自由读取 DOM、Cookie、调用 API。

这就是 XSS 被视为最危险的 Web 漏洞之一的原因——它直接绕过了同源策略这个最基础的防线。

防御 XSS 的手段:

  • 输入过滤 + 输出转义:所有用户输入都不信任
  • Content-Security-Policy (CSP):限制页面可以加载和执行的资源来源
  • HttpOnly Cookie:阻止 JS 读取敏感 Cookie

7. 实战

7.1 问答题

Q1:http://a.com 请求 https://a.com/api 会跨域吗?

会。协议不同(http vs https),端口也不同(80 vs 443)。域名相同不代表同源。

Q2:请求带了 Authorization: Bearer xxx,这是简单请求吗?

不是。Authorization 不在 CORS 安全请求头列表中。浏览器会先发 OPTIONS 预检。

Q3:为什么 Postman 不会遇到跨域?

跨域是浏览器的同源策略行为。Postman 是独立的 HTTP 客户端,不执行同源策略,所以没有跨域限制。

Q4:CORS 预检请求可以减少吗?

可以。设置 Access-Control-Max-Age 缓存预检结果。Chrome 上限 7200 秒,Firefox 上限 86400 秒。在缓存有效期内,相同请求不会重复预检。

Q5:Access-Control-Allow-Origin 可以设置多个域名吗?

不可以。这个响应头只能是单个域名或 *。要支持多域名,需要在服务端动态判断请求的 Origin 头,匹配后返回对应域名。

Q6:跨域请求,服务端到底收没收到?

收到了。跨域请求是正常的 HTTP 请求,服务端会接收并处理。浏览器只是拦截了 JS 读取响应。用服务端日志或抓包工具可以验证请求确实到达了。

Q7:<img> 可以加载跨域图片,为什么 Canvas 中的跨域图片会 “ 污染 “?

<img> 加载跨域图片是被允许的(展示层)。但如果将跨域图片绘制到 Canvas 中,然后用 canvas.toDataURL() 读取像素数据,浏览器会阻止——因为这属于 “ 通过脚本读取跨域数据 “。解决方案是图片服务设置 Access-Control-Allow-Origin,并在 <img> 标签加上 crossorigin="anonymous"

Q8:CORS 和 CORB 有什么区别?

CORS 在渲染进程中检查响应头,决定 JS 能否读取响应。CORB 在网络进程中检查 MIME 类型,直接阻止可疑的跨域响应(如 JSON、HTML)进入渲染进程的内存。CORB 是防 Spectre 侧信道攻击的额外防线。

7.2 真实世界案例

  • Nginx 反向代理消灭跨域:美团、字节等公司的 BFF 层用 Nginx 将前端和 API 服务统一到同一域名下,从架构层面彻底消除跨域。
  • 微前端的跨域治理:蚂蚁金服的 qiankun 框架在加载子应用时,需要子应用的静态资源配置 Access-Control-Allow-Origin,否则主应用无法 fetch 子应用的 HTML/JS。
  • CDN 字体跨域:Google Fonts 等 CDN 必须设置 Access-Control-Allow-Origin: *,因为 @font-face 加载字体文件受同源策略约束。自建字体 CDN 如果忘了配这个头,字体会加载失败但没有明显报错。
  • 第三方登录的 CORS 困境:OAuth 回调通常使用重定向(302)而非 AJAX,部分原因就是为了避开 CORS 限制。如果用 AJAX 去请求 OAuth 授权页面,由于第三方不会给你配 CORS 头,请求必然失败。