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 的原生支持,可通过全局配置或注解方式开启。

全局配置(WebMvcConfigurer)

@Configuration  
public class CorsGlobalConfig implements WebMvcConfigurer {  
    @Override  
    public void addCorsMappings(CorsRegistry registry) {  
        registry.addMapping("/api/**")                // 应用到所有 /api 路径  
                .allowedOrigins("https://your-domain.com") // 允许的源  
                .allowedMethods("GET", "POST", "PUT")      // 允许的方法  
                .allowedHeaders("Content-Type", "Authorization")  
                .exposedHeaders("X-Total-Count")  
                .allowCredentials(true)                     // 允许携带 Cookie  
                .maxAge(3600);                               // 预检结果缓存 1 小时  
    }  
}

注解方式(@CrossOrigin)

@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 之前拦截预检请求,导致跨域失败。

全局安全配置

@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 来实现动态源配置方案,这里以读取环境变量举例:

@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 安全头信息增强

@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 安全配置方案)允许该前端域名。
  • 前端请求示例:
// 带凭证的跨域请求  
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

后端返回:

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 生产环境检查清单

  1. 禁用 allowedOrigins("*")
  2. 设置合理的 maxAge 值(建议 ≤ 1 小时)
  3. 严格限制 allowedHeaders 范围
  4. 启用 allowCredentials 时必须指定具体源
  5. 结合 CSRF 保护关键操作

8. 结语

CORS 配置的本质是在便利性与安全性之间寻找平衡点。通过 Spring Security 的精细化配置,我们可以:

  1. 建立清晰边界:精确控制可交互的客户端范围
  2. 实施最小授权:按需开放必要的 HTTP 方法和头部
  3. 实现动态防御:根据环境变化自动调整安全策略
  4. 构建纵深防御:与 CSRF、CSP 等安全机制形成合力
# 生产环境推荐配置示例  
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 项目中,按需灵活地设定跨域安全边界,既满足前后端分离的开发需求,又确保系统不被非授权域滥用。让您的应用将拥有更健壮的跨域防护能力。

最后修改:2026 年 03 月 12 日
如果觉得我的文章对你有用,请随意赞赏