1. 前言 在日常开发中,跨域资源共享(CORS)已成为前端与后端分离架构的必备能力。正确配置 CORS 是后端安全边界的重要一环。但安全与便利的天平往往难以把握——过于宽松的配置可能导致敏感数据泄露,过于严格的策略又会影响正常业务交互。本文我们继续《Spring Security 实战教程》,讲解 CORS 安全配置。
2. 背景与原理概述 跨域请求是指浏览器在一个域(Origin A)加载的脚本访问另一个域(Origin B)的资源。为保护用户数据,浏览器默认禁止跨域访问,除非服务器在响应头中明确允许。CORS 机制通过 Access-Control-Allow-* 系列头部,告诉浏览器何种跨域请求被允许,从而实现安全的跨域通信。
2.1 预检请求全流程 当发起“简单请求”时(如 GET/POST 且只有简单头部),浏览器会直接带上 Cookie 等凭证;对复杂请求(如 PUT/DELETE、携带自定义头部)则先发起一次预检(OPTIONS)请求 ,确认服务器同意后才发送实际请求。
2.2 关键响应头说明 Access-Control-Allow-* 响应头主要包括以下几个:
响应头
安全意义
示例值
Access-Control-Allow-Origin
允许的源列表
https://your-domain.com
Access-Control-Allow-Methods
允许的HTTP方法
GET,POST,PUT
Access-Control-Allow-Headers
允许的请求头
Content-Type,Authorization
Access-Control-Max-Age
预检结果缓存时间(秒)
86400
3. Spring MVC 中的 CORS 支持 Spring Framework 自 4.2 起在 MVC 层面引入了对 CORS 的原生支持,可通过全局配置或注解方式开启。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class CorsGlobalConfig implements WebMvcConfigurer { @Override public void addCorsMappings (CorsRegistry registry) { registry.addMapping("/api/**" ) .allowedOrigins("https://your-domain.com" ) .allowedMethods("GET" , "POST" , "PUT" ) .allowedHeaders("Content-Type" , "Authorization" ) .exposedHeaders("X-Total-Count" ) .allowCredentials(true ) .maxAge(3600 ); } }
注解方式(@CrossOrigin) 1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RequestMapping("/users") @CrossOrigin( origins = "https://your-domain.com", methods = {RequestMethod.GET, RequestMethod.POST}, allowCredentials = "true", maxAge = 1800 ) public class UserController { @GetMapping public List<User> list () { … } }
4. Spring Security 安全配置方案 尽管 Spring MVC 已处理了 CORS,但如果启用了 Spring Security,必须在 Spring Security 配置中显式开启 CORS 支持 ,否则安全过滤器会在 MVC 之前拦截预检请求,导致跨域失败。
全局安全配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .cors(cors -> cors .configurationSource(corsConfigurationSource()) ) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ); return http.build(); } @Bean CorsConfigurationSource corsConfigurationSource () { CorsConfiguration config = new CorsConfiguration (); config.setAllowedOrigins(List.of("https://your-domain.com" )); config.setAllowedMethods(List.of("GET" , "POST" )); config.setAllowedHeaders(List.of("Authorization" , "Content-Type" )); config.setExposedHeaders(List.of("X-Custom-Header" )); config.setMaxAge(3600L ); config.setAllowCredentials(true ); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); source.registerCorsConfiguration("/**" , config); return source; } }
5. 安全强化最佳实践 5.1 动态源配置方案 很多时候我们并不希望相关配置是写死在代码里的,可以构建 CorsConfigurationSource 来实现动态源配置方案,这里以读取环境变量举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Bean CorsConfigurationSource corsConfigurationSource (Environment env) { CorsConfiguration config = new CorsConfiguration (); String allowedOrigins = env.getProperty("cors.allowed.origins" , "" ); config.setAllowedOrigins(Arrays.asList(allowedOrigins.split("," ))); config.applyPermitDefaultValues(); config.setAllowCredentials(true ); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); source.registerCorsConfiguration("/**" , config); return source; }
5.2 安全头信息增强 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Bean SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .cors(withDefaults()) .headers(headers -> headers .httpStrictTransportSecurity(hsts -> hsts .includeSubDomains(true ) .preload(true ) .maxAgeInSeconds(63072000 ) ) .xssProtection(xss -> xss .headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK) ) .contentSecurityPolicy(csp -> csp .policyDirectives("default-src 'self'" ) ) ); return http.build(); }
6. 前后端联合调试方案 假设前端部署在 https://frontend.app,后端 API 接口在 https://your-domain.com:
后端全局配置(见第4节 Spring Security 安全配置方案)允许该前端域名。
前端请求示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 fetch ('https://your-domain.com/data' , { method : 'GET' , credentials : 'include' , headers : { 'Content-Type' : 'application/json' , 'Authorization' : `Bearer ${token} ` } }) .then (response => { console .log ('Allowed Headers:' , response.headers .get ('Access-Control-Allow-Headers' )); });
先发起 OPTIONS https://your-domain.com/data,带上 Origin: https://frontend.app。
后端返回: 1 2 3 4 Access-Control-Allow-Origin: https://frontend.app Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET,POST,PUT,DELETE Access-Control-Allow-Headers: Content-Type,Authorization
7. 常见安全隐患与对策 7.1 风险矩阵
错误配置
潜在风险
解决方案
Access-Control-Allow-Origin: *
敏感数据泄露
动态白名单机制
允许全部HTTP方法
未授权操作风险
最小权限原则
暴露敏感响应头
信息泄露风险
严格控制 exposedHeaders
缺失 Vary: Origin 头
缓存投毒攻击
自动添加 Vary 头
7.2 生产环境检查清单
禁用 allowedOrigins("*")
设置合理的 maxAge 值(建议 ≤ 1 小时)
严格限制 allowedHeaders 范围
启用 allowCredentials 时必须指定具体源
结合 CSRF 保护关键操作
8. 结语 CORS 配置的本质是在便利性与安全性之间寻找平衡点。通过 Spring Security 的精细化配置,我们可以:
建立清晰边界 :精确控制可交互的客户端范围
实施最小授权 :按需开放必要的 HTTP 方法和头部
实现动态防御 :根据环境变化自动调整安全策略
构建纵深防御 :与 CSRF、CSP 等安全机制形成合力
1 2 3 4 cors.allowed.origins =https://web-client.com,https://mobile-client.com cors.max-age =3600 cors.exposed-headers =X-Request-ID
通过本文内容,相信小伙伴们已经可以在 Spring Boot + Spring Security 项目中,按需灵活地设定跨域安全边界,既满足前后端分离的开发需求,又确保系统不被非授权域滥用。让您的应用将拥有更健壮的跨域防护能力。