788 字
2 分钟
CORS 与浏览器安全:跨域资源共享详解
某天,前端团队在将应用从单域名迁移到微前端架构后,所有跨子域的 API 请求突然失败。浏览器控制台满屏的 CORS 错误让开发者措手不及。问题根源在于:新架构下前端部署在 app.example.com,API 服务在 api.example.com,浏览器同源策略将它们视为不同源,默认禁止跨域读取响应。要修复这个问题,需要深入理解 CORS 的工作机制,而不是简单地设置 Access-Control-Allow-Origin: *。
一、同源策略
1. 源的定义
同源策略(Same-Origin Policy,SOP)是浏览器安全模型的基石。两个 URL 同源,当且仅当协议、域名、端口三者完全相同。
| 属性 | 说明 | 示例 |
|---|---|---|
| 协议 | http / https | https |
| 域名 | 主机名 | api.example.com |
| 端口 | 端口号 | 8080 |
https://api.example.com:8080└─┬──┘ └───────┬─────┘ ┊协议 域名 端口2. 同源策略架构
graph TB
subgraph "浏览器安全模型"
A[同源策略 SOP] --> B[DOM 访问控制]
A --> C[网络请求控制]
A --> D[数据存储隔离]
B --> B1[禁止跨域 DOM 读取]
C --> C1[CORS 机制]
C --> C2[预检请求]
D --> D1[localStorage 隔离]
D --> D2[Cookie 作用域]
D --> D3[IndexedDB 隔离]
end
Info
同源策略是浏览器安全的基石,配合 CSP 策略,构建完整的客户端安全体系。
3. 跨域判断
| URL A | URL B | 同源? |
|---|---|---|
| https://a.com | https://b.com | 域名不同 |
| https://a.com:80 | https://a.com:8080 | 端口不同 |
| https://a.com | http://a.com | 协议不同 |
| https://api.a.com | https://a.com | 子域名不同 |
| https://a.com | https://a.com:443 | 默认端口,同源 |
4. 受限操作
// 以下操作受同源策略限制fetch("https://other-domain.com/api"); // 跨域请求localStorage.setItem("key", "value"); // 跨域存储Cookie.set("token", "abc"); // 跨域 Cookie二、CORS 机制
1. 简单请求
sequenceDiagram
participant B as Browser
participant S as Server
participant C as API Server
B->>C: GET /api/data
Note over C: 无 CORS 头
C--xB: 响应(浏览器拦截)
B->>S: GET /api/data
Note over S: 有 CORS 头
S-->>B: Access-Control-Allow-Origin: *
B->>S: 使用响应
简单请求条件:
- GET、HEAD、POST 之一
- 仅使用简单请求头
- Content-Type 为
application/x-www-form-urlencoded、multipart/form-data或text/plain
2. 预检请求
sequenceDiagram
participant B as Browser
participant S as API Server
B->>S: OPTIONS /api/data Preflight Request
Note over B: Access-Control-Request-Method: POST
Note over B: Access-Control-Request-Headers: content-type,authorization
S-->>B: 200 OK
Note over S: Access-Control-Allow-Origin: https://my-site.com
Note over S: Access-Control-Allow-Methods: POST,GET,OPTIONS
Note over S: Access-Control-Allow-Headers: content-type,authorization
Note over S: Access-Control-Max-Age: 3600
B->>S: POST /api/data (Actual Request)
S-->>B: Response
3. 请求头对照表
| 客户端请求头 | 说明 |
|---|---|
| Origin | 请求来源 |
| Access-Control-Request-Method | 预检请求的方法 |
| Access-Control-Request-Headers | 预检请求的自定义头 |
| 服务端响应头 | 说明 |
|---|---|
| Access-Control-Allow-Origin | 允许的来源 |
| Access-Control-Allow-Methods | 允许的方法 |
| Access-Control-Allow-Headers | 允许的头 |
| Access-Control-Max-Age | 预检缓存时间(秒) |
| Access-Control-Allow-Credentials | 是否允许携带凭证 |
三、凭证与 Cookie
1. 发送凭证
// 前端设置 credentialsfetch("https://api.example.com", { credentials: "include", // 包含 Cookie});# 服务端允许凭证response["Access-Control-Allow-Origin"] = "https://my-site.com" # 不能是 *response["Access-Control-Allow-Credentials"] = "true"2. Cookie 作用域
Set-Cookie: session=abc123; Domain=.example.com; # 子域可访问 Path=/; # 路径 HttpOnly; # 禁止 JS 访问 Secure; # 仅 HTTPS SameSite=Lax # CSRF 保护3. SameSite 属性
| 值 | 说明 | 场景 |
|---|---|---|
| Strict | 仅同站请求发送 | 高度敏感操作 |
| Lax | 导航请求发送 | 大多数场景(推荐) |
| None | 所有请求发送 | 需要跨域 API |
flowchart TB
subgraph "SameSite Cookie 行为"
A[请求发起] --> B{SameSite 值}
B -->|Strict| C[仅同站请求发送]
C --> C1[跨站链接不发送]
C --> C2[跨站表单不发送]
C --> C3[跨站 AJAX 不发送]
B -->|Lax| D[导航请求发送]
D --> D1[顶级导航发送]
D --> D2[GET 表单发送]
D --> D3[POST/跨站 AJAX 不发送]
B -->|None| E[所有请求发送]
E --> E1[必须配合 Secure]
E --> E2[需 CORS Allow-Credentials]
end
Info
SameSite 是防御 CSRF 攻击的重要机制,结合 OAuth2 与 OIDC 的授权流程使用时需特别注意配置。
四、服务端实现
1. Express 中间件
# Python/Flask 实现@app.before_requestdef handle_cors(): if request.method == "OPTIONS": response = make_response("", 200) response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,OPTIONS" response.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization" response.headers["Access-Control-Max-Age"] = "3600" return response
# 普通请求添加 CORS 头 response.headers["Access-Control-Allow-Origin"] = ALLOWED_ORIGIN2. 白名单验证
ALLOWED_ORIGINS = { "https://my-site.com", "https://admin.my-site.com"}
def validate_origin(origin: str) -> bool: if origin in ALLOWED_ORIGINS: return True # 支持子域名匹配 if origin.endswith(".my-site.com"): return True return False五、常见问题与调试
1. 常见错误
Access to fetch at 'https://api.example.com' from origin'https://my-site.com' has been blocked by CORS policy
No 'Access-Control-Allow-Origin' header is present onthe requested resource.2. 调试方法
// 在浏览器控制台查看详细错误// 1. Network 面板查看预检请求// 2. Console 查看 CORS 错误详情// 3. 验证 Origin 和 Allow-Origin 是否匹配3. 常见误区
| 误区 | 正确做法 |
|---|---|
| CORS 是服务端限制 | CORS 是浏览器的安全策略 |
| CORS 阻止所有跨域 | 只阻止被浏览器拦截的请求 |
设置 * 就能解决 | 凭证请求不能用 * |
| 预检只在 POST 时 | 所有非简单请求都会预检 |
六、安全考虑
1. CORS 配置安全
# 不安全的配置response.headers["Access-Control-Allow-Origin"] = "*"
# 安全的配置ALLOWED_ORIGINS = {"https://my-site.com"}if origin in ALLOWED_ORIGINS: response.headers["Access-Control-Allow-Origin"] = origin2. CSP 与 CORS 协同
graph LR
subgraph "浏览器安全策略协同"
A[HTTP 请求] --> B{CSP 检查}
B -->|允许| C{CORS 检查}
B -->|拒绝| D[阻止请求]
C -->|通过| E[允许响应]
C -->|失败| F[拦截响应]
end
subgraph "CSP 指令"
G[connect-src] --> G1[限制 AJAX 目标]
H[default-src] --> H1[默认资源来源]
end
subgraph "CORS 响应头"
I[Access-Control-Allow-Origin]
J[Access-Control-Allow-Credentials]
end
<!-- Content Security Policy --><meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self' https://api.example.com;"/>七、实际应用场景
| 场景 | CORS 配置 |
|---|---|
| 公共 API | Access-Control-Allow-Origin: * |
| 需要登录 | Allow-Origin: https://my-site.com + Allow-Credentials: true |
| 多域名 | 动态验证 Origin 并返回对应值 |
| SPA 应用 | 服务端配置允许多个预定义域名 |
八、总结
CORS 是解决浏览器同源策略限制的标准方案,通过特定的 HTTP 头实现安全的跨域访问。理解预检请求、凭证携带和 SameSite 属性,是前端开发者必备的技能。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
CORS 与浏览器安全:跨域资源共享详解
https://blog.souloss.com/posts/web/web-cors-and-browser-security/ 部分信息可能已经过时
相关文章 智能推荐
1
GraphQL 与 REST:API 设计范式的对比与选择
Web 技术深入 深度解读 GraphQL 与 REST 的设计哲学——从 Schema 定义到 N+1 问题,从订阅机制到缓存策略,帮助你在实际项目中做出合理的技术选型
2
为什么 CSS 选择器从右向左解析
技术科普 深入解析 CSS 选择器从右向左解析的性能优化原理,理解浏览器渲染引擎的设计考量。
3
为什么 WebSocket 需要握手
技术科普 深入解析 WebSocket 握手协议的设计原理,理解从 HTTP 到 WebSocket 的协议升级机制。
4
为什么 React 使用虚拟 DOM
技术科普 深入解析 React 虚拟 DOM 的设计原理,理解声明式 UI 与命令式 DOM 操作的本质区别。
5
为什么 JavaScript 是单线程的
技术科普 深入解析 JavaScript 单线程设计的历史背景和工程考量,理解事件循环机制如何实现高效的异步编程。






