文章目录
发现问题
在Web应用开发过程中,前端页面通过浏览器调用第三方API接口时,触发了跨域资源共享(CORS)限制,导致请求被拦截。具体报错信息如下:blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
首先使用Postman测试接口,确认接口本身正常可用。进一步研究发现,Postman作为API测试工具不受浏览器同源策略限制,而浏览器环境存在CORS安全机制。
浏览器 vs Postman 跨域行为差异原理
- 核心原理对比
| 特性 | 浏览器 | Postman |
|---|---|---|
| 同源策略 | ✅ 强制执行 | ❌ 不执行 |
| CORS 机制 | ✅ 必须遵守 | ❌ 不需要 |
| 安全沙箱 | ✅ 有 | ❌ 无 |
| 应用场景 | 用户日常上网 | 开发者调试工具 |
- 技术层面的差异
浏览器的安全机制:
是
否
是
否
前端应用
浏览器渲染引擎
安全检查
是否同源?
允许请求
发送 OPTIONS 预检请求
服务器返回正确
CORS 头?
允许实际请求
阻塞请求并报错
Postman 的本质:
Postman
操作系统网络接口
发送原始 HTTP 请求
接收原始 HTTP 响应
显示结果给开发者
CORS
CORS(Cross-Origin Resource Sharing,跨源资源共享) 是一种基于 HTTP 头的机制,允许服务器指定哪些源(协议+域名+端口)可以访问其资源。
javascript
// "源" 的定义
const origin = protocol + "://" + hostname + ":" + port;
// 同源示例
const origin1 = "https://example.com:443"; // 默认端口可省略
const origin2 = "https://example.com"; // 同源
// 不同源示例
"http://example.com" // 协议不同 (http vs https)
"https://api.example.com" // 子域名不同
"https://example.com:8080" // 端口不同
工作流程
服务器 浏览器 客户端 服务器 浏览器 客户端 发起跨域请求 alt [简单请求] [预检请求] fetch('https://other.com/api', options) 直接发送真实请求 Origin: https://client.com 响应 + CORS 头 根据 CORS 头决定是否返回响应 OPTIONS 预检请求 Origin: https://client.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-Custom 204 No Content + CORS 头 发送真实请求 真实响应 + CORS 头 返回响应数据
响应头详解
| 响应头 | 必需/可选 | 作用 | 示例 | 场景 |
|---|---|---|---|---|
| Access-Control-Allow-Origin | ⭐ 必需 | 允许的来源 | https://frontend.com | 所有跨域请求 |
| Access-Control-Allow-Methods | ⭐ 预检必需 | 允许的HTTP方法 | GET, POST, OPTIONS | 预检请求(OPTIONS)响应 |
| Access-Control-Allow-Headers | ⭐ 预检必需 | 允许的请求头 | Content-Type, Authorization | 预检请求中使用了非简单头部 |
| Access-Control-Allow-Credentials | ⭐ 凭证必需 | 是否允许凭证 | true | 请求需要携带凭证(cookies、认证头) |
| Access-Control-Expose-Headers | ✅ 可选 | 暴露的响应头 | X-Total-Count | 需要前端访问自定义响应头时 |
| Access-Control-Max-Age | ✅ 可选 | 预检缓存时间 | 86400 (缓存 24 小时) | 优化性能,减少预检请求 |
| Vary: Origin | ✅ 推荐 | 缓存区分 | Origin |
-
Access-Control-Allow-Origin
作用: 指定允许访问资源的来源
详细说明:- 允许所有域名(最简单,但不安全)
- http://example.com:允许特定域名
- http://localhost:8080:允许本地开发服务器
示例:
http# 语法 Access-Control-Allow-Origin: <origin> | * # 示例 Access-Control-Allow-Origin: * Access-Control-Allow-Origin: http://localhost:3000浏览器行为:
xml客户端请求头:Origin: http://localhost:8080 服务器响应头:Access-Control-Allow-Origin: http://localhost:8080 ✓ 匹配成功,允许访问 -
Access-Control-Allow-Methods
作用: 允许客户端使用哪些 HTTP 方法。
详细说明:- OPTIONS 必须包含,用于预检请求
- 常用的 RESTful 方法:
- GET:获取资源
- POST:创建资源
- PUT:更新资源
- DELETE:删除资源
- PATCH:部分更新(可选)
- HEAD:获取头部信息(可选)
示例:
xml
# 语法
Access-Control-Allow-Methods: <method>[, <method>]*
# 示例
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
-
Access-Control-Allow-Headers
作用: 允许客户端发送哪些自定义请求头
详细说明: 当请求包含不在列表中的头部时,浏览器会阻止请求常见需要允许的头部:
头部 作用 是否必需 Authorization 认证令牌(Bearer token) ✓ 必需 Content-Type 请求体类型(JSON/表单等) ✓ 必需 Accept 客户端接受的响应类型 推荐 X-Requested-With 标识 AJAX 请求 推荐 Cache-Control 缓存控制 可选 X-CSRF-Token CSRF 防护 可选 示例:
html# 语法 Access-Control-Allow-Headers: <header-name>[, <header-name>]* # 示例 Access-Control-Allow-Headers: Content-Type, Authorization # 浏览器发送的预检请求 OPTIONS /api/data HTTP/1.1 Access-Control-Request-Headers: Content-Type, X-Custom-Header Origin: https://frontend.com # 服务器响应必须包含这些头 Access-Control-Allow-Headers: Content-Type, X-Custom-Header -
Access-Control-Allow-Credentials
作用: 是否允许携带凭证(cookies、HTTP认证)
重要规则:- 不能与 Access-Control-Allow-Origin: * 同时使用
- 必须与具体的域名配合
语法:
bashAccess-Control-Allow-Credentials: true | false -
Access-Control-Expose-Headers
作用: 允许浏览器访问哪些响应头
背景: 默认情况下,浏览器只能访问以下"简单响应头":Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
其他头部(如自定义头部)需要显式暴露。
bash# 语法 Access-Control-Expose-Headers: <header-name>[, <header-name>]* # 示例:暴露自定义头 Access-Control-Expose-Headers: X-Total-Count, X-Custom-Header # 前端可以访问 fetch('/api/data') .then(response => { console.log(response.headers.get('X-Total-Count')); // ✅ 可以访问 console.log(response.headers.get('X-Custom-Header')); // ✅ 可以访问 }); -
Access-Control-Max-Age
作用: 预检请求的缓存时间(秒)bash# 语法 Access-Control-Max-Age: <delta-seconds> # 示例 Access-Control-Max-Age: 86400 # 缓存 24 小时 Access-Control-Max-Age: 600 # 缓存 10 分钟 # 效果: # 1. 浏览器缓存预检响应结果 # 2. 在有效期内,相同请求不再发送预检请求 # 3. 减少网络请求,提高性能 # 建议值: # - 开发环境:300(5分钟) # - 生产环境:86400(24小时) -
Vary
作用: 正确缓存不同来源的响应bash# ❌ 没有设置 Vary: Origin,可能导致缓存污染 # 用户A访问后,用户B可能看到A的CORS设置 # ✅ 正确配置 Access-Control-Allow-Origin: https://frontend.com Vary: Origin
总结
CORS 错误总是服务器配置问题,浏览器只是在执行安全策略保护用户安全。
调整后测试环境CORS配置:
xml
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Credentials" value="false" />
<add name="Access-Control-Allow-Headers" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
<add name="Access-Control-Expose-Headers" value="*" />
</customHeaders>
</httpProtocol>