网络 & HTTP
跨域(CORS)
浏览器的同源策略限制了不同源之间的资源访问。CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器和服务器协商的标准机制,允许服务器声明哪些源可以访问其资源。
同源策略
两个 URL 同源要求协议、域名、端口三者完全一致。
| URL | 与 https://example.com/page 同源? | 原因 |
|---|---|---|
https://example.com/other | ✅ | 同协议、同域名、同端口 |
http://example.com/page | ❌ | 协议不同(http vs https) |
https://api.example.com/page | ❌ | 子域名不同 |
https://example.com:8080/page | ❌ | 端口不同 |
https://other.com/page | ❌ | 域名不同 |
同源策略限制的是读取跨域响应,而不是发出请求。跨域请求可以发出,服务端会处理,但浏览器会拦截响应,不让页面脚本读取。
请求分类
CORS 将跨域请求分为两类,处理流程不同。
简单请求
同时满足以下条件的请求直接发送,无需预检:
- 方法为
GET、POST或HEAD - 请求头仅包含
Accept、Accept-Language、Content-Language、Content-Type Content-Type限于text/plain、multipart/form-data、application/x-www-form-urlencoded
浏览器在请求头中携带 Origin,服务端响应中包含 Access-Control-Allow-Origin 时浏览器才放行:
请求:
Origin: https://app.example.com
响应:
Access-Control-Allow-Origin: https://app.example.com预检请求(Preflight)
不满足简单请求条件的请求(如 PUT、DELETE、携带自定义头、Content-Type: application/json),浏览器先发一个 OPTIONS 请求询问服务端是否允许,通过后再发实际请求。
第一步:浏览器发 OPTIONS 预检
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
第二步:服务端响应允许
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
第三步:浏览器发实际请求
PUT /api/data HTTP/1.1
Origin: https://app.example.com
Content-Type: application/jsonAccess-Control-Max-Age 指定预检结果的缓存时间(秒),避免每次请求都触发预检。
CORS 响应头
| 响应头 | 作用 | 示例值 |
|---|---|---|
Access-Control-Allow-Origin | 允许的源,* 表示所有源 | https://app.example.com |
Access-Control-Allow-Methods | 允许的 HTTP 方法 | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers | 允许的请求头 | Content-Type, Authorization |
Access-Control-Allow-Credentials | 是否允许携带 Cookie | true |
Access-Control-Expose-Headers | 允许前端读取的响应头 | X-Custom-Header |
Access-Control-Max-Age | 预检结果缓存时间(秒) | 86400 |
Access-Control-Allow-Origin 设为 * 时,不能同时使用 Access-Control-Allow-Credentials: true,浏览器会拒绝。
Cookie 跨域
默认情况下跨域请求不携带 Cookie。需要同时在前端和服务端开启:
// 前端:请求时设置 withCredentials
fetch('https://api.example.com/data', {
credentials: 'include',
})
// axios
axios.get('https://api.example.com/data', {
withCredentials: true,
})# 服务端响应头(必须指定具体源,不能用 *)
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true服务端配置
Nginx
location /api/ {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
add_header Access-Control-Allow-Credentials true always;
if ($request_method = OPTIONS) {
add_header Access-Control-Max-Age 86400;
return 204;
}
proxy_pass http://backend;
}Node.js(Express)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.setHeader('Access-Control-Allow-Credentials', 'true')
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Max-Age', '86400')
return res.sendStatus(204)
}
next()
})或使用 cors 中间件:
import cors from 'cors'
app.use(cors({
origin: 'https://app.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
}))开发环境代理
开发时前端和后端通常端口不同,产生跨域。最简单的方案是在开发服务器配置代理,请求由服务端转发,浏览器看到的是同源请求。
Vite
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},
}webpack-dev-server
// webpack.config.js
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},代理只在开发环境有效,生产环境仍需服务端正确配置 CORS 响应头,或将前后端部署到同一域名下。