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;
}三个经典坑(排查顺序):
- 只配了 CorsFilter 没在 Security 链放行:预检 OPTIONS 请求先撞到认证被 401(无 Authorization 头的预检必死)——Security 链里
.cors(...)让 CorsFilter 跑在认证之前(它排在链前部),这是正确解; allowCredentials(true)与allowedOrigins("*")组合:规范禁止,浏览器直接报错——用allowedOriginPatterns列具体域;- 甩锅 CORS:看到的「CORS 错误」常常是后端 500/401 被浏览器以 CORS 形式呈现——先看 Network 里真正的响应状态码,再查 CORS 头。
预检 304 缓存失效/每次都 OPTIONS 的性能问题:maxAge 拉长 + 前端代理同域转发(开发环境 vite proxy 根治)。