mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
788 字
2 分钟
CORS 与浏览器安全:跨域资源共享详解
2024-03-15

某天,前端团队在将应用从单域名迁移到微前端架构后,所有跨子域的 API 请求突然失败。浏览器控制台满屏的 CORS 错误让开发者措手不及。问题根源在于:新架构下前端部署在 app.example.com,API 服务在 api.example.com,浏览器同源策略将它们视为不同源,默认禁止跨域读取响应。要修复这个问题,需要深入理解 CORS 的工作机制,而不是简单地设置 Access-Control-Allow-Origin: *

一、同源策略#

1. 源的定义#

同源策略(Same-Origin Policy,SOP)是浏览器安全模型的基石。两个 URL 同源,当且仅当协议、域名、端口三者完全相同。

属性说明示例
协议http / httpshttps
域名主机名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. 跨域判断#

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-urlencodedmultipart/form-datatext/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是否允许携带凭证

1. 发送凭证#

// 前端设置 credentials
fetch("https://api.example.com", {
credentials: "include", // 包含 Cookie
});
# 服务端允许凭证
response["Access-Control-Allow-Origin"] = "https://my-site.com" # 不能是 *
response["Access-Control-Allow-Credentials"] = "true"
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_request
def 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_ORIGIN

2. 白名单验证#

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 on
the 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"] = origin

2. 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 配置
公共 APIAccess-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/
作者
Souloss
发布于
2024-03-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时