CORS(跨域资源共享)核心配置项
CORS 是浏览器为解决跨域请求限制制定的规范,核心通过服务端响应头控制跨域权限,前端需配合特定配置(如携带 Cookie 时)。以下是服务端核心配置项及说明:
| 响应头 | 作用 | 示例值 |
|---|---|---|
Access-Control-Allow-Origin |
允许跨域的源(必填),支持具体域名/*(通配符,不可携带 Cookie) |
https://www.example.com / *
|
Access-Control-Allow-Methods |
允许的请求方法(必填,预检请求用) | GET,POST,PUT,DELETE,OPTIONS |
Access-Control-Allow-Headers |
允许的自定义请求头(预检请求用) | Content-Type,Authorization,Token |
Access-Control-Allow-Credentials |
是否允许携带 Cookie/认证信息(布尔值,需配合前端配置) | true |
Access-Control-Max-Age |
预检请求(OPTIONS)的缓存时间(秒),减少预检请求次数 |
86400(24小时) |
Access-Control-Expose-Headers |
允许前端获取的自定义响应头(默认仅能获取6个基础头) | X-Total-Count,Token |
预检请求(OPTIONS):当跨域请求为「非简单请求」(如 PUT/DELETE、带自定义头、Content-Type 为 application/json)时,浏览器会先发送 OPTIONS 请求校验权限,通过后再发真实请求。
配置「携带 Cookie」的完整步骤
携带 Cookie 是跨域中高频且易出错的场景,必须满足前端 + 服务端双向配置,缺一不可:
前提条件
-
Access-Control-Allow-Origin不能用*,必须指定具体的前端域名(如https://localhost:3000); - 服务端需设置
Access-Control-Allow-Credentials: true; - 前端请求需显式开启「携带凭证」(如
withCredentials: true); - 浏览器要求:跨域 Cookie 需设置
SameSite=None+Secure(HTTPS 环境),否则浏览器会拦截。
步骤1:服务端配置(主流后端示例)
示例1:Node.js/Express
const express = require('express');
const app = express();
// 全局跨域中间件
app.use((req, res, next) => {
// 1. 允许指定前端域名跨域(不可用*)
res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
// 2. 允许携带Cookie
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 3. 允许的请求方法
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
// 4. 允许的请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
// 5. 预检请求缓存时间
res.setHeader('Access-Control-Max-Age', '86400');
// 处理预检请求(OPTIONS)
if (req.method === 'OPTIONS') {
return res.sendStatus(200); // 预检请求无需返回内容
}
next();
});
// 示例接口:设置Cookie并返回数据
app.get('/api/user', (req, res) => {
// 设置跨域Cookie(需SameSite=None + Secure,HTTPS环境)
res.cookie('token', 'abc123', {
httpOnly: true, // 防止XSS
secure: true, // HTTPS环境必填
sameSite: 'None', // 跨域必填
maxAge: 24 * 60 * 60 * 1000 // 1天
});
res.json({ name: '张三', code: 200 });
});
app.listen(8080, () => console.log('服务端运行在8080端口'));
示例2:Java/Spring Boot
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口
.allowedOrigins("https://localhost:3000") // 允许的前端域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Content-Type", "Authorization")
.allowCredentials(true) // 允许携带Cookie
.maxAge(86400); // 预检缓存时间
}
}
示例3:Nginx 反向代理配置(前端通过Nginx转发请求时)
server {
listen 80;
server_name api.example.com;
location / {
# 跨域配置
add_header Access-Control-Allow-Origin "https://localhost:3000" always;
add_header Access-Control-Allow-Credentials "true" 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-Max-Age "86400" always;
# 处理预检请求
if ($request_method = OPTIONS) {
return 204;
}
# 转发到后端服务
proxy_pass http://127.0.0.1:8080;
proxy_cookie_domain 127.0.0.1 example.com; # 调整Cookie的Domain
}
}
步骤2:前端配置(主流前端框架示例)
示例1:原生 Fetch API
fetch('https://api.example.com/api/user', {
method: 'GET',
credentials: 'include', // 关键:携带Cookie(同域可用same-origin,跨域需include)
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
示例2:Axios(Vue/React常用)
import axios from 'axios';
// 全局配置(推荐)
axios.defaults.withCredentials = true; // 关键:开启携带Cookie
// 或单个请求配置
axios.get('https://api.example.com/api/user', {
withCredentials: true // 关键
})
.then(res => console.log(res.data))
.catch(err => console.error(err));
三、常见问题 & 解决方案
1. 问题1:浏览器提示「The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'」
- 原因:
Access-Control-Allow-Origin设为*但同时开启了Allow-Credentials: true; - 解决:将
Allow-Origin改为具体前端域名(如https://localhost:3000)。
-
问题2:Cookie 已返回但前端无法携带
- 原因1:Cookie 未设置
SameSite=None+Secure(HTTPS 环境); - 原因2:前端未设置
withCredentials: true; - 解决:补全 Cookie 配置 + 前端开启凭证携带。
- 原因1:Cookie 未设置
-
问题3:预检请求(OPTIONS)返回404/500
- 原因:服务端未处理 OPTIONS 请求(非简单请求会先发OPTIONS);
- 解决:服务端对 OPTIONS 请求直接返回 200/204 状态码(无需业务逻辑)。
-
问题4:跨域时 Cookie 的 Domain 不匹配
- 原因:Cookie 的 Domain 是服务端域名(如
api.example.com),前端域名是www.example.com; - 解决:设置 Cookie 的 Domain 为顶级域名(如
.example.com),或通过 Nginx 调整proxy_cookie_domain。
- 原因:Cookie 的 Domain 是服务端域名(如
总结
- CORS 核心是服务端响应头配置,前端仅需配合开启凭证携带;
- 携带 Cookie 必须满足:
- 服务端:
Allow-Origin为具体域名 +Allow-Credentials: true+ Cookie 配置SameSite=None+Secure; - 前端:
withCredentials: true(Fetch 用credentials: 'include');
- 服务端:
- 非简单请求需处理 OPTIONS 预检请求,避免返回 404/500。
什么是预检请求?它在CORS跨域中起到什么作用?
一、什么是预检请求(Preflight Request)
预检请求是浏览器在发送跨域的「非简单请求」前,自动发起的一次 OPTIONS 方法的请求,用于提前向服务端校验:当前跨域请求是否被允许、请求方法/头信息是否合法。只有预检请求通过(服务端返回合法的 CORS 响应头),浏览器才会发送真正的业务请求;若预检失败,浏览器会直接抛出跨域错误,不会执行真实请求。
1. 区分「简单请求」和「非简单请求」
浏览器对跨域请求分为两类,只有非简单请求才会触发预检:
| 类型 | 判定条件(需同时满足) | 示例 |
|---|---|---|
| 简单请求 | 1. 请求方法为:GET/POST/HEAD;2. 请求头仅包含: Accept/Accept-Language/Content-Language/Content-Type(仅限 application/x-www-form-urlencoded/multipart/form-data/text/plain);3. 无自定义请求头(如 Authorization/Token)。 |
GET 请求、普通 POST 表单提交 |
| 非简单请求 | 不满足「简单请求」任一条件的请求 |
PUT/DELETE 请求、Content-Type: application/json 的 POST 请求、带 Authorization 头的请求 |
2. 预检请求的特征
- 请求方法:
OPTIONS(HTTP 方法); - 请求头包含:
-
Origin:当前前端域名(如https://localhost:3000); -
Access-Control-Request-Method:真实请求要使用的方法(如PUT); -
Access-Control-Request-Headers:真实请求要携带的自定义头(如Content-Type,Authorization);
-
- 无请求体(仅做权限校验)。
二、预检请求在 CORS 中的核心作用
预检请求是浏览器的安全机制,核心目的是保护服务端免受未授权的跨域请求攻击,同时明确服务端的跨域权限边界,具体作用包括:
1. 提前校验权限,避免无效请求
服务端通过预检请求的响应头,明确告知浏览器:是否允许当前源的跨域请求、允许的方法/头信息是什么。如果权限不匹配,浏览器直接拦截真实请求,避免服务端处理无效的跨域业务请求,减少服务端资源消耗。
2. 明确服务端的跨域策略
预检请求让服务端有机会声明自己的 CORS 规则(如允许的源、方法、头),浏览器会缓存这些规则(通过 Access-Control-Max-Age 配置缓存时间),后续相同的跨域请求无需重复发送预检,提升性能。
3. 防止跨域请求的「意外行为」
比如服务端原本只支持 GET 请求,但前端发起了 DELETE 跨域请求,预检请求会提前发现这种不匹配,避免服务端因处理不支持的请求方法而产生错误。
三、预检请求的完整流程(示例)
以「前端发送 PUT 请求(Content-Type: application/json)到跨域服务端」为例:
-
浏览器触发预检:
由于PUT方法属于非简单请求,浏览器先发送 OPTIONS 预检请求:OPTIONS /api/user/1 HTTP/1.1 Origin: https://localhost:3000 Access-Control-Request-Method: PUT Access-Control-Request-Headers: Content-Type Host: api.example.com -
服务端响应预检:
服务端返回包含 CORS 头的响应,告知浏览器权限:HTTP/1.1 200 OK Access-Control-Allow-Origin: https://localhost:3000 Access-Control-Allow-Methods: GET,POST,PUT,DELETE Access-Control-Allow-Headers: Content-Type,Authorization Access-Control-Max-Age: 86400 Access-Control-Allow-Credentials: true -
浏览器校验并发送真实请求:
浏览器检查响应头:- 若
Access-Control-Allow-Origin包含当前源、Allow-Methods包含PUT、Allow-Headers包含Content-Type,则校验通过,发送真实的PUT请求; - 若校验失败,抛出
Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy错误。
- 若
服务端处理真实请求并响应:
服务端处理PUT请求,返回业务数据,并携带 CORS 头(如Allow-Origin)。
四、预检请求的常见配置与优化
1. 服务端处理预检请求的关键
- 对
OPTIONS方法的请求,无需执行业务逻辑,直接返回 200/204 状态码即可; - 必须返回正确的 CORS 响应头(如
Allow-Origin/Allow-Methods/Allow-Headers)。
2. 优化:设置预检缓存时间
通过 Access-Control-Max-Age 头配置预检请求的缓存时间(单位:秒),比如设置为 86400(24小时),浏览器在缓存期内对相同的跨域请求不再重复发送预检,减少请求次数:
// Node.js/Express 示例
res.setHeader('Access-Control-Max-Age', '86400');
3. 常见问题:预检请求返回 404/500
- 原因:服务端未专门处理
OPTIONS方法,导致预检请求被当作普通请求处理,返回 404 或 500; - 解决:在服务端的跨域中间件中,对
OPTIONS请求直接返回 200 状态码(如 Express 中if (req.method === 'OPTIONS') return res.sendStatus(200))。
五、总结
- 预检请求是浏览器针对非简单跨域请求的前置校验机制,通过 OPTIONS 方法发起;
- 核心作用:提前校验服务端跨域权限,保护服务端安全,减少无效请求;
- 只有预检请求通过,浏览器才会发送真实的业务请求;
- 服务端需正确处理 OPTIONS 请求,并通过
Access-Control-Max-Age优化预检缓存,提升性能。
简单来说,预检请求就像「跨域请求的安检」,浏览器先问服务端「这个请求能不能过」,服务端同意后,才会让真实请求通行。