浏览器跨域问题:从原理到实战
浏览器的同源策略是 Web 安全的基石。它规定:只有当两个 URL 的 协议(Protocol)、域名(Host)、端口(Port) 三者完全一致时,才被视为"同源"。 以 假设没有同源策略: 这就是经典的 CSRF(跨站请求伪造) 攻击的基础。同源策略确保 如果允许跨域 DOM 访问: 同源策略本质上实现了一个沙箱隔离机制:每个源(origin)都在自己的沙箱里运行,无法访问其他沙箱的数据。这是最小权限原则在浏览器中的体现。 最常见的场景。前端部署在 企业常见需求——用户登录一次即可访问多个子系统: SSO 涉及的跨域问题包括: Cookie 共享: 不同团队的微应用部署在不同子域,需要在主应用中集成: 服务器通过 HTTP 响应头声明"允许哪些源访问我的资源": 简单请求 vs 预检请求(Preflight): 通过 Nginx 等反向代理,让前端和 API 看起来在同一个源: 这样前端访问 利用 用于 iframe、window.open 等场景的安全跨域通信: 适用于 适用于 使用你的域名搭建如下环境: 或者本地演示: https://gist.github.com/vistart/1ad7113fcabe411a545536795374d7d6一、什么是跨域?
同源策略(Same-Origin Policy)
https://app.rho.im 为基准:目标 URL 是否同源 原因 https://app.rho.im/api/data✅ 同源 协议、域名、端口均相同 http://app.rho.im/api❌ 跨域 协议不同(http vs https) https://api.rho.im/data❌ 跨域 域名不同(子域名也算不同源) https://app.rho.social/page❌ 跨域 域名完全不同 https://app.rho.im:8443/api❌ 跨域 端口不同(443 vs 8443) 同源策略限制了什么?
注意:同源策略不阻止请求发出,而是阻止浏览器读取响应。服务器实际上收到了请求并返回了响应,只是浏览器拒绝将响应交给 JS 代码。这个细微区别非常重要。
二、为什么需要同源策略?——安全性分析
场景 1:防止敏感数据窃取
bank.com,浏览器存有会话 Cookieevil.comevil.com 的 JS 向 bank.com/api/account 发请求(浏览器会自动带上 Cookie)evil.com 就能读取你的银行账户信息evil.com 无法读取 bank.com 的响应。场景 2:防止 DOM 篡改
恶意网站嵌入 <iframe src="bank.com/login">
→ JS 读取 iframe 中用户输入的密码
→ 密码被发送到攻击者服务器核心安全原则
三、常见跨域场景
1. 前后端分离架构
app.rho.im,API 服务在 api.rho.im:前端 https://app.rho.im → fetch("https://api.rho.im/users") → 跨域!2. 单点登录(SSO)
认证中心: https://sso.rho.im
应用 A: https://app1.rho.im
应用 B: https://app2.rho.socialsso.rho.im 设置的 Cookie,app1.rho.im 能否读取?.rho.im)下可以通过设置 domain=.rho.im 共享.rho.im vs .rho.social)之间无法直接共享 Cookie3. 第三方服务集成
你的网站 https://app.rho.im
→ 调用地图 API: https://api.mapbox.com
→ 嵌入支付页面: https://pay.stripe.com
→ 加载字体: https://fonts.googleapis.com4. 微前端架构
主应用: https://portal.rho.im
微应用 A: https://team-a.rho.im
微应用 B: https://team-b.rho.social四、跨域解决方案
方案 1:CORS(跨域资源共享)— 最标准的方案
Access-Control-Allow-Origin: https://app.rho.im
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: trueOPTIONS 请求询问服务器是否允许,通过后才发送实际请求浏览器 服务器
|-- OPTIONS /api/data -------->| ← 预检请求
|<-- 200 + CORS Headers ------| ← 服务器回复"允许"
|-- PUT /api/data ------------>| ← 实际请求
|<-- 200 + Data ---------------|方案 2:反向代理 — 最常用的生产方案
server {
server_name app.rho.im;
# 前端静态资源
location / {
root /var/www/frontend;
}
# API 请求代理到后端服务
location /api/ {
proxy_pass http://backend-server:3000/;
}
}https://app.rho.im/api/users 实际被代理到后端,浏览器看到的始终是同源请求。方案 3:JSONP — 历史遗留方案
<script> 标签不受同源策略限制的特点。仅支持 GET,已基本被 CORS 取代,了解即可。方案 4:postMessage — 跨窗口通信
// 父窗口 (https://app.rho.im) 向 iframe 发消息
iframe.contentWindow.postMessage({ type: 'login', token: 'xxx' }, 'https://sso.rho.social');
// iframe (https://sso.rho.social) 接收消息
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.rho.im') return; // 验证来源!
console.log(event.data);
});方案 5:Cookie 跨域策略
策略 适用场景 示例 domain=.rho.im同一父域下的子域共享 sso.rho.im ↔ app.rho.imSameSite=None; Secure跨站 Cookie 传递 第三方登录 Token(JWT)方案 完全不同的域 rho.im ↔ rho.social五、SSO 单点登录的跨域实现
方案 A:共享 Cookie(同父域)
*.rho.im 下的多个子域:1. 用户访问 app1.rho.im → 未登录 → 重定向到 sso.rho.im/login
2. 用户在 sso.rho.im 登录成功
3. sso.rho.im 设置 Cookie: Set-Cookie: token=xxx; Domain=.rho.im; Path=/
4. 用户回到 app1.rho.im → 浏览器自动带上 Cookie → 已登录
5. 用户访问 app2.rho.im → Cookie 同样可用 → 自动登录方案 B:CAS / OAuth 重定向(跨域)
rho.im 与 rho.social 之间:1. 用户访问 app.rho.social → 未登录
2. 重定向到 sso.rho.im/authorize?redirect_uri=https://app.rho.social/callback
3. 用户在 sso.rho.im 登录(或已登录)
4. sso.rho.im 重定向回 app.rho.social/callback?code=AUTHORIZATION_CODE
5. app.rho.social 后端用 code 换 token(后端到后端,不受同源策略限制)
6. app.rho.social 设置自己域的 Cookie 或返回 JWT方案 C:前端 Token + postMessage
1. 主应用打开隐藏 iframe 指向 sso.rho.im/check-session
2. sso.rho.im 检查自己的 Cookie → 如已登录,通过 postMessage 回传 token
3. 主应用收到 token → 存入内存 → 完成登录六、教学演示建议
准备工作
https://app.rho.im → 前端应用(端口 8080)
https://api.rho.im → 后端 API(端口 3000)
https://sso.rho.social → SSO 认证中心http://localhost:8080 → 前端
http://localhost:3000 → API(跨端口 = 跨域)演示步骤
credentials: 'include')配套的交互式演示代码