Skip to content

CORS 跨域的原理是什么?Spring Security 里怎么正确配置? ​

  • 标签:Spring Security、认证授权、后端

🏛 业务架构师 ​

CORS(Cross-Origin Resource Sharing)原理与 Spring Security 配置:

原理:浏览器的同源策略默认禁止网页的 JS 读取跨域响应(防恶意站点偷数据);CORS 是 W3C 标准的"放行机制"——浏览器在跨域请求时自动附加 Origin 头,服务器响应中声明 Access-Control-Allow-Origin 等头,浏览器据此判断放行与否。关键认知:CORS 是浏览器执行的保护,服务器该收到的请求都收到了——所以它防的是"浏览器端数据被偷",不是接口防盗(防盗要靠认证/签名)。非简单请求(带 Authorization、Content-Type: application/json)会先发 OPTIONS 预检(preflight),服务器批准后才发真实请求。

Spring Security 里的正确配置(与安全链整合):

java
@Bean
SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource corsSource) {
    http.cors(c -> c.configurationSource(corsSource))   // 接管 CORS
        .authorizeHttpRequests(a -> a.anyRequest().authenticated());
    return http.build();
}

@Bean
CorsConfigurationSource corsSource() {
    CorsConfiguration cfg = new CorsConfiguration();
    cfg.setAllowedOriginPatterns(List.of("https://app.example.com")); // 白名单,别用 *
    cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
    cfg.setAllowedHeaders(List.of("Authorization", "Content-Type"));
    cfg.setAllowCredentials(true);   // 带 Cookie 时 origin 不能是 *
    UrlBasedCorsConfigurationSource src = new UrlBasedCorsConfigurationSource();
    src.registerCorsConfiguration("/**", cfg);
    return src;
}

高频坑(生产事故清单):allowedOrigins("*") + allowCredentials(true) 组合非法(浏览器直接拒绝)——用 originPatterns 白名单;OPTIONS 预检被安全链 401 拦截(预检不带 Token)——把 OPTIONS 放行(permitAll for OPTIONS 或 CorsFilter 放最前);多个 CorsFilter 重复叠加(自定义的与 Security 的打架)——统一交给 Security 的 cors()。安全视角:CORS 白名单是攻击面管理的一部分,别图省事开 *——允许的源越少越安全。

🏗 系统架构师 ​

CORS(跨域资源共享)= 浏览器同源策略下的受控跨域访问机制。原理:

  • 同源策略:协议+域名+端口一致才同源——跨域的 XHR/fetch 响应被浏览器拦截(服务器其实已响应,是浏览器丢弃);
  • 预检(Preflight):非简单请求(带自定义 header 如 Authorization、非简单 Method、JSON Content-Type)触发浏览器先发 OPTIONS 探问:Origin + Access-Control-Request-Method/Headers;
  • 服务器授权:响应 Access-Control-Allow-Origin / -Methods / -Headers / -Max-Age——浏览器比对放行真实请求;带凭据(Cookie)时必须 Allow-Credentials: true 且 Origin 不许为 *(要精确域名)——这是最常见配置错误。

Spring Security 中的正确配置(过滤器在认证链最前,预检请求不得被 401 挡住):

java
@Bean
SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource source) throws Exception {
    http.cors(c -> c.configurationSource(source))   // 接管 CORS(关掉默认的=放行链)
        .authorizeHttpRequests(a -> a
            .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()  // 预检放行
            .anyRequest().authenticated());
    return http.build();
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration cfg = new CorsConfiguration();
    cfg.setAllowedOrigins(List.of("https://app.example.com"));  // 精确域名,带凭据禁 *
    cfg.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
    cfg.setAllowedHeaders(List.of("Authorization","Content-Type"));
    cfg.setAllowCredentials(true);
    cfg.setMaxAge(3600L);                            // 预检缓存,减少 OPTIONS
    UrlBasedCorsConfigurationSource src = new UrlBasedCorsConfigurationSource();
    src.registerCorsConfiguration("/**", cfg);
    return src;
}

架构提醒:CORS 是浏览器侧机制不是服务端认证(防不了服务器到服务器的调用)——它的安全语义是"保护浏览器用户的凭据不被任意网站滥用",与 CSRF 防御联动;允许 Origin 清单要配置化(多环境不同前端域);网关层统一 CORS(微服务架构在网关配一次,避免各服务各配导致 header 重复——重复的 Allow-Origin 会再次被浏览器拒绝)。

💻 全栈开发专家 ​

CORS 原理:浏览器同源策略限制跨域读响应——跨域请求(协议/域名/端口任一不同)时,浏览器先看服务端是否授权(CORS 响应头):简单请求直接发、看响应头;非简单请求(自定义头/JSON POST)先发 OPTIONS 预检,服务器放行才发真请求。关键认知:CORS 是浏览器行为,服务端不拦截跨域请求本身——curl/Postman 无感。

Spring Security 里怎么正确配置(6.x Lambda DSL):

java
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
    http.cors(c -> c.configurationSource(corsSource()))  // 启用CORS读配置
        .csrf(c -> c.disable())
        ...;
    return http.build();
}

@Bean
CorsConfigurationSource corsSource() {
    CorsConfiguration cfg = new CorsConfiguration();
    cfg.setAllowedOriginPatterns(List.of("https://*.example.com")); // 不用*(配凭证时必须具体)
    cfg.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
    cfg.setAllowedHeaders(List.of("*"));
    cfg.setAllowCredentials(true);        // 带cookie时 origin 绝不能是 *
    cfg.setExposedHeaders(List.of("Authorization"));
    cfg.setMaxAge(3600L);                 // 预检结果缓存,减少OPTIONS
    UrlBasedCorsConfigurationSource src = new UrlBasedCorsConfigurationSource();
    src.registerCorsConfiguration("/**", cfg);
    return src;
}

三个经典坑(排查顺序):

  1. 只配了 CorsFilter 没在 Security 链放行:预检 OPTIONS 请求先撞到认证被 401(无 Authorization 头的预检必死)——Security 链里 .cors(...) 让 CorsFilter 跑在认证之前(它排在链前部),这是正确解;
  2. allowCredentials(true) 与 allowedOrigins("*") 组合:规范禁止,浏览器直接报错——用 allowedOriginPatterns 列具体域;
  3. 甩锅 CORS:看到的「CORS 错误」常常是后端 500/401 被浏览器以 CORS 形式呈现——先看 Network 里真正的响应状态码,再查 CORS 头。

预检 304 缓存失效/每次都 OPTIONS 的性能问题:maxAge 拉长 + 前端代理同域转发(开发环境 vite proxy 根治)。