跨域处理(CORS)
✨ 基于 Spring CORS Filter 的开箱即用跨域解决方案,一个注解搞定全局跨域配置。
- 🎯 注解驱动:通过
@EnableDefaultGlobalCors一键启用全局跨域 - 🔧 默认配置:内置安全合理的 CORS 默认配置
- 🚀 最高优先级:
HIGHEST_PRECEDENCE确保 CORS 最先执行 - 🏭 工厂模式:采用
FactoryBean模式管理 Filter 生命周期 - 🛡️ 安全性:支持凭证传递、预检请求缓存等安全特性
- ⚙️ 易扩展:支持自定义 CORS 配置覆盖默认行为
快速开始
启用跨域处理
在 Spring Boot 启动类上添加 @EnableDefaultGlobalCors 注解即可启用全局跨域处理:
import host.springboot.framework.autoconfigure.filter.annotation.EnableDefaultGlobalCors;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDefaultGlobalCors // 启用全局跨域处理
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}验证效果
启动应用后,所有接口都会自动添加 CORS 响应头:
# 发送跨域请求
curl -X GET http://localhost:8080/api/user/list \
-H "Origin: http://example.com" \
-v
# 响应头中会包含:
# Access-Control-Allow-Origin: http://example.com
# Access-Control-Allow-Credentials: true
# Access-Control-Max-Age: 3600预检请求(OPTIONS):
# 发送预检请求
curl -X OPTIONS http://localhost:8080/api/user/create \
-H "Origin: http://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type,Authorization" \
-v
# 响应头中会包含:
# Access-Control-Allow-Origin: http://example.com
# Access-Control-Allow-Methods: GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE
# Access-Control-Allow-Headers: Content-Type,Authorization
# Access-Control-Max-Age: 3600核心架构
工作流程
HTTP 请求(带 Origin 头)
↓
DefaultCorsFilter(最高优先级)
↓
检查是否为跨域请求
↓
预检请求(OPTIONS)?
├─ 是 → 返回 CORS 响应头 + 200
│ (Access-Control-Allow-*)
│
└─ 否 → 实际请求
↓
添加 CORS 响应头
↓
继续执行后续过滤器
↓
执行 Controller 方法
↓
返回响应(包含 CORS 头)核心类说明
| 类名 | 职责 | 说明 |
|---|---|---|
DefaultCorsFilter | 默认跨域过滤器 | 继承 Spring 的 CorsFilter,实现 FactoryBean 和 PriorityOrdered |
ConfigurationSource | CORS 配置源 | 管理默认的 CORS 配置策略 |
@EnableDefaultGlobalCors | 启用注解 | 导入 DefaultCorsFilter 到 Spring 容器 |
执行优先级
Filter 执行顺序(Order 数字越小越先执行):
1. DefaultCorsFilter (HIGHEST_PRECEDENCE = Integer.MIN_VALUE)
↓
2. RequestBodyFilter (ORDER = -10000)
↓
3. XssFilter (ORDER = -9000)
↓
4. 其他过滤器...
↓
5. Controller 方法执行💡 为什么是最高优先级?
- CORS 预检请求需要尽早响应,避免进入业务逻辑
- 确保所有请求都能正确添加 CORS 响应头
- 防止其他过滤器拦截预检请求
默认 CORS 配置
配置详情
框架提供的默认 CORS 配置如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
| allowedOriginPattern | * | 允许所有域名访问 |
| allowedMethods | GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE | 支持所有常见 HTTP 方法 |
| allowedHeaders | * | 允许所有请求头 |
| allowCredentials | true | 允许携带 Cookie 和认证凭证 |
| maxAge | 3600 秒(1小时) | 预检请求结果的缓存时间 |
| mappings | /** | 应用到所有路径 |
源码分析
public static CorsConfiguration defaultCorsConfiguration() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 1. 允许的域(使用 Pattern 支持更灵活的匹配)
corsConfiguration.addAllowedOriginPattern("*");
// 2. 允许的请求方式(支持所有常见 HTTP 方法)
corsConfiguration.addAllowedMethod(HttpMethod.GET);
corsConfiguration.addAllowedMethod(HttpMethod.HEAD);
corsConfiguration.addAllowedMethod(HttpMethod.POST);
corsConfiguration.addAllowedMethod(HttpMethod.PUT);
corsConfiguration.addAllowedMethod(HttpMethod.PATCH);
corsConfiguration.addAllowedMethod(HttpMethod.DELETE);
corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS);
corsConfiguration.addAllowedMethod(HttpMethod.TRACE);
// 3. 允许的头信息(所有请求头)
corsConfiguration.addAllowedHeader("*");
// 4. 预检请求的有效期(1小时)
corsConfiguration.setMaxAge(Duration.ofHours(1));
// 5. 是否支持安全证书(Cookie、Authorization 等)
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}配置说明
1. allowedOriginPattern vs allowedOrigin
// ❌ 旧方式:addAllowedOrigin("*")
// 当 allowCredentials=true 时,浏览器会拒绝 allowedOrigin="*"
// ✅ 新方式:addAllowedOriginPattern("*")
// 支持通配符模式,与 allowCredentials=true 兼容
corsConfiguration.addAllowedOriginPattern("*");Pattern 支持的格式:
*- 匹配所有域名http://localhost:*- 匹配所有本地端口https://*.example.com- 匹配所有 example.com 的子域名
2. allowCredentials 详解
corsConfiguration.setAllowCredentials(true);作用:
- 允许前端携带 Cookie
- 允许前端携带 Authorization 等认证头
- 响应头包含:
Access-Control-Allow-Credentials: true
前端配置(必须配合使用):
// Fetch API
fetch('http://api.example.com/user/info', {
credentials: 'include' // 必须设置,否则不会发送 Cookie
});
// Axios
axios.defaults.withCredentials = true;3. maxAge 预检缓存
corsConfiguration.setMaxAge(Duration.ofHours(1));作用:
- 缓存预检请求(OPTIONS)的结果 1 小时
- 减少重复的预检请求,提升性能
- 响应头包含:
Access-Control-Max-Age: 3600
工作原理:
第一次跨域请求:
1. 浏览器发送 OPTIONS 预检请求
2. 服务器响应 CORS 头 + Max-Age: 3600
3. 浏览器缓存预检结果 1 小时
1 小时内的后续请求:
- 浏览器直接发送实际请求,无需预检
- 节省一次网络往返
1 小时后:
- 缓存过期,重新发送预检请求自定义 CORS 配置
方式1:完全自定义 CorsFilter
如果默认配置不满足需求,可以自定义 CorsFilter Bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.time.Duration;
import java.util.Arrays;
@Configuration
public class CustomCorsConfiguration {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 1. 仅允许特定域名
config.addAllowedOriginPattern("https://*.example.com");
config.addAllowedOriginPattern("http://localhost:*");
// 2. 仅允许特定 HTTP 方法
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
// 3. 仅允许特定请求头
config.setAllowedHeaders(Arrays.asList(
"Content-Type",
"Authorization",
"X-Requested-With"
));
// 4. 暴露特定响应头给前端
config.setExposedHeaders(Arrays.asList(
"X-Total-Count",
"X-Page-Number"
));
// 5. 允许携带凭证
config.setAllowCredentials(true);
// 6. 预检缓存 2 小时
config.setMaxAge(Duration.ofHours(2));
// 应用到所有路径
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}方式2:针对不同路径配置不同策略
@Configuration
public class MultiPathCorsConfiguration {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 公开 API:宽松配置
CorsConfiguration publicConfig = new CorsConfiguration();
publicConfig.addAllowedOriginPattern("*");
publicConfig.addAllowedMethod("*");
publicConfig.addAllowedHeader("*");
publicConfig.setAllowCredentials(false); // 公开 API 不需要凭证
source.registerCorsConfiguration("/api/public/**", publicConfig);
// 管理后台 API:严格配置
CorsConfiguration adminConfig = new CorsConfiguration();
adminConfig.setAllowedOrigins(Arrays.asList("https://admin.example.com"));
adminConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
adminConfig.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization"));
adminConfig.setAllowCredentials(true);
source.registerCorsConfiguration("/api/admin/**", adminConfig);
// 用户 API:中等配置
CorsConfiguration userConfig = new CorsConfiguration();
userConfig.addAllowedOriginPattern("https://*.example.com");
userConfig.addAllowedOriginPattern("http://localhost:*");
userConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT"));
userConfig.addAllowedHeader("*");
userConfig.setAllowCredentials(true);
userConfig.setMaxAge(Duration.ofHours(1));
source.registerCorsConfiguration("/api/user/**", userConfig);
return new CorsFilter(source);
}
}方式3:使用 WebMvcConfigurer
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}⚠️ 注意:如果自定义了 CorsFilter Bean,框架的 @EnableDefaultGlobalCors 将不会生效。
常见场景
场景1:前后端分离开发
需求:前端本地开发(http://localhost:3000),后端本地运行(http://localhost:8080)
// 方式1:使用默认配置(推荐)
@SpringBootApplication
@EnableDefaultGlobalCors
public class Application {
// 默认配置已支持所有域名,包括 localhost
}
// 方式2:自定义配置
@Configuration
public class DevCorsConfig {
@Bean
@Profile("dev") // 仅开发环境启用
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("http://localhost:*");
config.addAllowedOriginPattern("http://127.0.0.1:*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}场景2:生产环境严格配置
需求:仅允许特定域名访问,防止跨域攻击
@Configuration
public class ProdCorsConfig {
@Bean
@Profile("prod") // 仅生产环境启用
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 仅允许官网域名
config.setAllowedOrigins(Arrays.asList(
"https://www.example.com",
"https://app.example.com",
"https://admin.example.com"
));
// 仅允许安全的 HTTP 方法
config.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE"
));
// 仅允许必要的请求头
config.setAllowedHeaders(Arrays.asList(
"Content-Type",
"Authorization",
"X-Requested-With"
));
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(2));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}场景3:特定接口禁用 CORS
需求:某些内部 API 不需要 CORS 支持
@Configuration
public class SelectiveCorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 公开 API 启用 CORS
CorsConfiguration publicConfig = new CorsConfiguration();
publicConfig.addAllowedOriginPattern("*");
publicConfig.addAllowedMethod("*");
publicConfig.addAllowedHeader("*");
publicConfig.setAllowCredentials(true);
source.registerCorsConfiguration("/api/public/**", publicConfig);
// 内部 API 不配置 CORS(浏览器同源策略会阻止跨域访问)
// source.registerCorsConfiguration("/api/internal/**", null);
return new CorsFilter(source);
}
}场景4:处理复杂的预检请求
需求:前端使用自定义请求头,需要在预检请求中声明
@Configuration
public class CustomHeaderCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
// 允许自定义请求头
config.setAllowedHeaders(Arrays.asList(
"Content-Type",
"Authorization",
"X-Requested-With",
"X-Custom-Token", // 自定义:业务 Token
"X-Device-Id", // 自定义:设备 ID
"X-App-Version" // 自定义:应用版本
));
// 暴露自定义响应头给前端
config.setExposedHeaders(Arrays.asList(
"X-Total-Count", // 分页总数
"X-Rate-Limit", // 限流剩余次数
"X-Request-Id" // 请求追踪 ID
));
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}前端对应配置(Axios):
axios.defaults.withCredentials = true;
// 发送自定义请求头
axios.get('/api/user/info', {
headers: {
'X-Custom-Token': 'your-token',
'X-Device-Id': 'device-123',
'X-App-Version': '1.0.0'
}
}).then(response => {
// 读取自定义响应头
const totalCount = response.headers['x-total-count'];
const rateLimit = response.headers['x-rate-limit'];
const requestId = response.headers['x-request-id'];
});场景5:微服务架构下的 CORS
需求:多个微服务共享统一的 CORS 配置
// 方式1:在网关统一处理 CORS
@Configuration
public class GatewayCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
// 方式2:每个微服务使用框架默认配置
@SpringBootApplication
@EnableDefaultGlobalCors
public class UserServiceApplication {
// 使用默认配置,简单快捷
}最佳实践
✅ 推荐做法
// 1. 开发环境使用宽松配置,生产环境使用严格配置
@Configuration
public class EnvironmentAwareCorsConfig {
@Bean
public CorsFilter corsFilter(@Value("${spring.profiles.active}") String profile) {
CorsConfiguration config = new CorsConfiguration();
if ("dev".equals(profile) || "test".equals(profile)) {
// 开发/测试:宽松配置
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
} else {
// 生产:严格配置
config.setAllowedOrigins(Arrays.asList(
"https://www.example.com",
"https://app.example.com"
));
config.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE"
));
config.setAllowedHeaders(Arrays.asList(
"Content-Type", "Authorization"
));
}
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
// 2. 使用 allowedOriginPattern 而不是 allowedOrigin
// ✅ 推荐
config.addAllowedOriginPattern("https://*.example.com");
// ❌ 不推荐(当 allowCredentials=true 时会失败)
config.addAllowedOrigin("*");
// 3. 合理设置预检缓存时间
config.setMaxAge(Duration.ofHours(1)); // 1小时,平衡性能和灵活性
// 4. 暴露必要的响应头给前端
config.setExposedHeaders(Arrays.asList(
"X-Total-Count",
"X-Request-Id"
));
// 5. 使用 Profile 区分环境
@Bean
@Profile("dev")
public CorsFilter devCorsFilter() {
// 开发环境配置
}
@Bean
@Profile("prod")
public CorsFilter prodCorsFilter() {
// 生产环境配置
}❌ 不推荐做法
// ❌ 1. 生产环境使用 allowedOrigin("*") + allowCredentials(true)
config.addAllowedOrigin("*"); // 浏览器会拒绝此配置
config.setAllowCredentials(true);
// ✅ 正确做法
config.addAllowedOriginPattern("*");
config.setAllowCredentials(true);
// ❌ 2. 忘记设置 allowCredentials
config.addAllowedOriginPattern("*");
// 前端无法发送 Cookie 和 Authorization
// ✅ 正确做法
config.setAllowCredentials(true);
// ❌ 3. 预检缓存时间过长
config.setMaxAge(Duration.ofDays(30)); // 太长,配置更新需要 30 天才能生效
// ✅ 正确做法
config.setMaxAge(Duration.ofHours(1)); // 1小时足够
// ❌ 4. 在 Controller 上使用 @CrossOrigin(分散配置,难以维护)
@RestController
@CrossOrigin(origins = "*") // 不推荐
public class UserController {
// ...
}
// ✅ 正确做法:使用全局 CorsFilter
@SpringBootApplication
@EnableDefaultGlobalCors
public class Application {
// ...
}
// ❌ 5. 忘记在前端设置 withCredentials
// JavaScript
fetch('/api/user/info'); // 不会发送 Cookie
// ✅ 正确做法
fetch('/api/user/info', {
credentials: 'include' // 发送 Cookie
});安全性考虑
1. 生产环境严格配置
@Configuration
@Profile("prod")
public class SecureCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// ⚠️ 严格限制允许的域名
config.setAllowedOrigins(Arrays.asList(
"https://www.example.com",
"https://app.example.com"
));
// 不要使用 config.addAllowedOriginPattern("*");
// ⚠️ 仅允许必要的 HTTP 方法
config.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE"
));
// 不要使用 config.addAllowedMethod("*");
// ⚠️ 仅允许必要的请求头
config.setAllowedHeaders(Arrays.asList(
"Content-Type",
"Authorization"
));
// 不要使用 config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}2. 动态域名白名单
@Configuration
public class DynamicCorsConfig {
@Value("${cors.allowed.origins}")
private List<String> allowedOrigins;
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 从配置文件读取允许的域名
config.setAllowedOrigins(allowedOrigins);
config.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE"
));
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}配置文件(application.yml):
# 开发环境
spring:
profiles:
active: dev
cors:
allowed:
origins:
- http://localhost:3000
- http://localhost:8080
- http://127.0.0.1:3000
---
# 生产环境
spring:
config:
activate:
on-profile: prod
cors:
allowed:
origins:
- https://www.example.com
- https://app.example.com
- https://admin.example.com3. CSRF 防护
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// CORS 配置
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// CSRF 配置
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**") // 公开 API 不需要 CSRF
)
// 其他安全配置...
;
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("https://www.example.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}常见问题
Q: 为什么启用了 CORS 但前端还是报错?
A: 常见原因及解决方案:
1. 前端未设置 withCredentials
// ❌ 错误
fetch('/api/user/info');
// ✅ 正确
fetch('/api/user/info', {
credentials: 'include'
});
// Axios
axios.defaults.withCredentials = true;2. allowedOrigin 和 allowCredentials 冲突
// ❌ 错误
config.addAllowedOrigin("*"); // 使用 allowedOrigin("*")
config.setAllowCredentials(true); // 浏览器会拒绝
// ✅ 正确
config.addAllowedOriginPattern("*"); // 使用 allowedOriginPattern("*")
config.setAllowCredentials(true);3. 多个 CorsFilter Bean 冲突
// 检查是否定义了多个 CorsFilter
// 只保留一个即可
@Bean
public CorsFilter corsFilter() {
// 自定义配置
}4. 预检请求被其他过滤器拦截
// 确保 CorsFilter 优先级最高
public class CustomCorsFilter extends CorsFilter implements Ordered {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}Q: 如何调试 CORS 问题?
A: 使用浏览器开发者工具检查:
1. 检查预检请求(OPTIONS)
# Network 面板筛选 OPTIONS 请求
# 查看请求头:
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
# 查看响应头(应该包含):
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: content-type, authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 36002. 检查实际请求
# 查看请求头:
Origin: http://localhost:3000
# 查看响应头(应该包含):
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true3. 常见错误信息
❌ "Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource."
→ 解决:检查是否启用了 CORS 配置
❌ "Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Origin' header in the response must not
be the wildcard '*' when the request's credentials mode is 'include'."
→ 解决:使用 allowedOriginPattern("*") 替代 allowedOrigin("*")
❌ "Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
The value of the 'Access-Control-Allow-Credentials' header in the response is ''
which must be 'true' when the request's credentials mode is 'include'."
→ 解决:设置 config.setAllowCredentials(true)Q: @EnableDefaultGlobalCors 和自定义 CorsFilter 有什么区别?
A: 对比如下:
| 特性 | @EnableDefaultGlobalCors | 自定义 CorsFilter |
|---|---|---|
| 使用难度 | 简单(一个注解) | 需要编写配置类 |
| 配置灵活性 | 固定(默认配置) | 完全自定义 |
| 适用场景 | 开发环境、快速启动 | 生产环境、精细控制 |
| 优先级 | HIGHEST_PRECEDENCE | 可自定义 |
| 多路径配置 | 不支持 | 支持 |
选择建议:
- 开发环境:使用
@EnableDefaultGlobalCors,快速启动 - 生产环境:自定义
CorsFilter,严格控制
Q: 如何在不同环境使用不同的 CORS 配置?
A: 使用 @Profile 注解:
// 开发环境:宽松配置
@Configuration
@Profile({"dev", "test"})
public class DevCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
// 生产环境:严格配置
@Configuration
@Profile("prod")
public class ProdCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("https://www.example.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}Q: 如何禁用 CORS?
A: 有两种方式:
方式1:不添加 @EnableDefaultGlobalCors 注解
@SpringBootApplication
// 不添加 @EnableDefaultGlobalCors
public class Application {
// CORS 不会启用
}方式2:不定义 CorsFilter Bean
// 删除或注释掉 CorsFilter Bean
// @Bean
// public CorsFilter corsFilter() {
// ...
// }Q: 预检请求(OPTIONS)过多影响性能怎么办?
A: 通过增加 maxAge 缓存预检结果:
config.setMaxAge(Duration.ofHours(2)); // 缓存 2 小时
// 浏览器会缓存预检结果,2 小时内的相同请求不再发送 OPTIONS建议值:
- 开发环境:
Duration.ofMinutes(30)- 30 分钟,便于调试 - 生产环境:
Duration.ofHours(1)- 1 小时,平衡性能和灵活性
Q: 如何允许前端读取自定义响应头?
A: 使用 setExposedHeaders() 暴露响应头:
config.setExposedHeaders(Arrays.asList(
"X-Total-Count", // 分页总数
"X-Request-Id", // 请求追踪 ID
"X-Rate-Limit", // 限流剩余次数
"X-Custom-Header" // 自定义响应头
));前端读取:
fetch('/api/user/list')
.then(response => {
const totalCount = response.headers.get('X-Total-Count');
const requestId = response.headers.get('X-Request-Id');
console.log('Total:', totalCount, 'RequestId:', requestId);
return response.json();
});💡 提示:
- CORS 是浏览器的安全机制,不影响服务端到服务端的调用
- 预检请求(OPTIONS)是浏览器自动发送的,开发者无需手动处理
allowedOriginPattern支持通配符,比allowedOrigin更灵活- 生产环境务必使用严格的域名白名单,避免安全风险
