深度解析 Spring MVC @CrossOrigin
注解
@CrossOrigin
是 Spring MVC 中处理跨域资源共享(CORS)的核心注解,它提供了一种声明式的方式来配置跨域请求策略。本文将全面剖析其工作原理、源码实现、使用场景及最佳实践。
一、注解定义与核心作用
1. 源码定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
@AliasFor("origins")
String[] value() default {};
@AliasFor("value")
String[] origins() default {};
String[] allowedHeaders() default {};
String[] exposedHeaders() default {};
RequestMethod[] methods() default {};
String allowCredentials() default "";
long maxAge() default -1;
}
2. 核心作用
- 跨域请求支持:启用跨源资源共享(CORS)
- 精细控制:细粒度配置跨域策略
- 安全机制:在保障安全的前提下实现跨域访问
- 简化开发:替代传统过滤器实现方式
二、CORS 工作原理
1. 跨域请求流程
2. 核心响应头
响应头 | 作用 | 示例值 |
---|---|---|
Access-Control-Allow-Origin | 允许的源 | * 或 https://example.com |
Access-Control-Allow-Methods | 允许的方法 | GET, POST, PUT |
Access-Control-Allow-Headers | 允许的请求头 | Content-Type, Authorization |
Access-Control-Expose-Headers | 暴露的响应头 | X-Custom-Header |
Access-Control-Allow-Credentials | 是否允许凭证 | true |
Access-Control-Max-Age | 预检缓存时间 | 3600 (秒) |
三、源码深度解析
1. 核心处理器:CorsProcessor
public class DefaultCorsProcessor implements CorsProcessor {
public boolean processRequest(CorsConfiguration config,
HttpServletRequest request,
HttpServletResponse response) {
// 1. 检查是否CORS请求
if (!CorsUtils.isCorsRequest(request)) {
return true;
}
// 2. 处理预检请求
if (CorsUtils.isPreFlightRequest(request)) {
return handlePreFlight(config, request, response);
}
// 3. 处理实际CORS请求
return handleActual(config, request, response);
}
private boolean handlePreFlight(CorsConfiguration config,
HttpServletRequest request,
HttpServletResponse response) {
// 设置CORS响应头
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN,
config.getAllowedOrigins().get(0));
if (config.getMaxAge() != null) {
response.setHeader(ACCESS_CONTROL_MAX_AGE,
config.getMaxAge().toString());
}
// ... 其他响应头处理
}
}
2. 注解解析器:CorsAnnotationProcessor
class CorsAnnotationProcessor {
public CorsConfiguration processAnnotation(CrossOrigin annotation) {
CorsConfiguration config = new CorsConfiguration();
// 解析注解属性
for (String origin : annotation.origins()) {
config.addAllowedOrigin(origin);
}
for (String header : annotation.allowedHeaders()) {
config.addAllowedHeader(header);
}
config.setAllowCredentials(Boolean.parseBoolean(annotation.allowCredentials()));
config.setMaxAge(annotation.maxAge());
return config;
}
}
3. 配置合并机制
public class HandlerMethodCorsConfiguration extends CorsConfiguration {
public HandlerMethodCorsConfiguration(CorsConfiguration globalConfig,
CorsConfiguration methodConfig) {
// 合并全局配置和方法级配置
this.combine(globalConfig);
if (methodConfig != null) {
this.combine(methodConfig);
}
}
private void combine(CorsConfiguration other) {
// 合并允许的来源
if (other.getAllowedOrigins() != null) {
this.setAllowedOrigins(other.getAllowedOrigins());
}
// 合并允许的方法
if (other.getAllowedMethods() != null) {
this.setAllowedMethods(other.getAllowedMethods());
}
// ... 其他属性合并
}
}
四、使用场景与最佳实践
1. 全局跨域配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com", "https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3600);
registry.addMapping("/public/**")
.allowedOrigins("*")
.allowedMethods("GET");
}
}
2. 控制器级别配置
@RestController
@CrossOrigin(origins = "https://app.example.com",
allowedHeaders = {"Content-Type", "Authorization"},
maxAge = 1800)
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public List<User> getUsers() {
return userService.getAllUsers();
}
}
3. 方法级别细粒度控制
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
@CrossOrigin(origins = "https://store.example.com")
public Product getProduct(@PathVariable Long id) {
return productService.getById(id);
}
@PostMapping
@CrossOrigin(origins = "https://admin.example.com",
allowedHeaders = "Content-Type, X-Admin-Token",
exposedHeaders = "X-RateLimit-Remaining",
allowCredentials = "true")
public Product createProduct(@RequestBody Product product) {
return productService.save(product);
}
}
4. 安全跨域配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("https://trusted-domain.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST"));
config.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
return config;
});
}
}
五、高级特性详解
1. 动态跨域策略
@RestController
public class DynamicCorsController {
@GetMapping("/dynamic")
public ResponseEntity<?> dynamicEndpoint(HttpServletRequest request) {
// 根据请求动态决定允许的源
String origin = request.getHeader("Origin");
if (isAllowedOrigin(origin)) {
return ResponseEntity.ok()
.header("Access-Control-Allow-Origin", origin)
.body("Dynamic response");
}
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
private boolean isAllowedOrigin(String origin) {
return origin != null && origin.endsWith("example.com");
}
}
2. CORS 与 OAuth2 整合
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
@Bean
public CorsFilter corsFilter() {
return new CorsFilter(request -> {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://oauth-client.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
return config;
});
}
}
3. 微服务架构中的 CORS
@Configuration
public class GatewayCorsConfig {
@Bean
public CorsWebFilter corsFilter() {
return new CorsWebFilter(exchange -> {
CorsConfiguration config = new CorsConfiguration();
// 允许所有前端应用域
config.addAllowedOrigin("https://frontend-app.com");
// 允许必要的请求头
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Authorization");
config.addAllowedHeader("X-Tenant-Id");
// 允许所有方法
config.addAllowedMethod("*");
// 设置缓存时间
config.setMaxAge(7200L);
return config;
});
}
}
六、最佳实践总结
1. 安全跨域配置建议
场景 | 推荐配置 | 说明 |
---|---|---|
公共API | allowedOrigins("*") | 只读接口无敏感数据 |
认证API | 指定具体域名 | 包含认证信息的接口 |
私有API | 企业内网域名 | 仅限内部使用 |
混合使用 | 全局开放 + 方法级限制 | 灵活控制 |
移动应用 | 使用通配符子域 *.example.com | 适配多端 |
2. 生产环境配置示例
@Configuration
public class ProductionCorsConfig implements WebMvcConfigurer {
@Value("${cors.allowed.origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE")
.allowedHeaders("Content-Type", "Authorization", "X-Requested-With")
.exposedHeaders("X-RateLimit-Limit", "X-RateLimit-Remaining")
.allowCredentials(true)
.maxAge(3600);
}
}
3. 性能优化策略
@Bean
public CorsFilter corsFilter() {
// 使用CorsFilter代替@CrossOrigin注解
// 在Filter层处理,性能更高
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("https://example.com");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setMaxAge(7200L); // 2小时预检缓存
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
七、常见问题解决方案
1. 预检请求失败
解决方案:
// 确保OPTIONS请求被处理
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
// 配置允许OPTIONS方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS"); // 包含OPTIONS
}
}
2. 凭证与通配符冲突
问题:Cannot use allowedOrigins "*" and allowCredentials true
// 解决方案1:使用具体域名
.allowedOrigins("https://client.com")
.allowCredentials(true)
// 解决方案2:动态确定源
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 动态添加允许的源
return config;
});
return new CorsFilter(source);
}
3. 复杂请求头处理
// 配置接受自定义头
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/complex/**")
.allowedOrigins("https://complex-app.com")
.allowedHeaders("Content-Type", "X-Custom-Header", "X-Extra-Header")
.exposedHeaders("X-Response-Code", "X-Response-Time");
}
八、未来发展方向
1. 安全增强:CORS + CSP 整合
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource())
.and()
.headers()
.contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'");
return http.build();
}
private CorsConfigurationSource corsConfigurationSource() {
// CORS配置
}
}
2. 智能 CORS 策略管理
@Bean
public CorsFilter smartCorsFilter(CorsPolicyService policyService) {
return new CorsFilter(request -> {
// 从中央策略服务获取配置
return policyService.getCorsConfig(request);
});
}
3. 边缘计算集成
@Configuration
@EnableEdgeComputing
public class EdgeCorsConfig {
@Bean
public CorsEdgeFilter edgeCorsFilter() {
return new CorsEdgeFilter(config -> {
config.setEdgeCaching(true);
config.setGlobalCorsRules(loadGlobalRules());
});
}
}
九、总结
@CrossOrigin
是 Spring MVC 实现跨域访问的关键技术,其核心价值在于:
- 便捷性:简化 CORS 配置,无需复杂过滤器
- 精细控制:支持全局、控制器、方法三级配置
- 安全性:在开放访问的同时保障资源安全
- 标准化:符合 W3C CORS 规范
在实际应用中应当:
- 限制来源:避免使用
*
作为来源,尤其是需要凭证时 - 明确方法:指定必要的 HTTP 方法
- 控制头部:限制必要的请求和响应头
- 利用缓存:设置合理的
maxAge
减少预检请求
在安全实践中:
- 生产环境禁用通配符:当
allowCredentials=true
时 - 结合 CSP:提供额外的安全层
- 监控异常:记录异常的 CORS 请求
- 定期审计:检查跨域策略是否符合安全要求
随着技术演进:
- 边缘计算:在 CDN 层实现 CORS
- 策略即代码:动态策略管理
- 零信任整合:与现代安全架构融合
- 协议演进:适应 HTTP/3 新特性
掌握 @CrossOrigin
的高级特性和最佳实践,能够帮助开发者:
- 构建跨域安全的现代化 Web 应用
- 实现微服务架构下的灵活跨域策略
- 优化跨域请求性能
- 满足合规性要求
在 Spring 生态中,随着 WebFlux 和 Spring Cloud Gateway 的发展,跨域处理机制也在不断创新,深入理解其原理和应用是构建高效能分布式系统的关键技能。