引言
在现代 Web 应用开发中,前后端分离架构已成为主流趋势。前端可能运行在 http://localhost:3000
的本地开发环境,而后端服务部署在 https://api.example.com
的独立域名下。当浏览器尝试从前端向后端发起请求时,开发者经常会遇到一个经典的错误提示:
Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy
这个问题的根源在于浏览器的同源策略(Same-Origin Policy),而 CORS(跨源资源共享)正是为了解决这一限制而设计的规范。作为 Java 后端开发者,深入理解 CORS 的工作原理、正确配置方法及其潜在的安全风险,已成为构建现代化 Web 应用的必备技能。
第一章:同源策略与跨域问题的本质
1.1 什么是同源策略
同源策略是浏览器最基础的安全机制之一,它规定:
-
当且仅当协议(HTTP/HTTPS)、域名(example.com)和端口(8080)完全相同时,才视为同源
-
跨域请求默认被禁止读取响应内容(但请求仍可能被发送到服务器)
示例对照表:
1.2 为什么需要跨域解决方案
早期的 Web 应用通过 JSONP 实现跨域请求,但其存在明显缺陷:
-
仅支持 GET 方法
-
缺乏错误处理机制
-
存在 XSS 注入风险
随着 RESTful API 的普及和复杂请求场景的增多,W3C 于 2014 年正式将 CORS 纳入标准,成为现代浏览器处理跨域请求的标准方案。
第二章:CORS 工作机制深度解析
2.1 简单请求与预检请求
浏览器将跨域请求分为两类:
简单请求(Simple Request)条件:
-
使用 GET、HEAD 或 POST 方法
-
仅包含以下请求头:
-
Accept
-
Accept-Language
-
Content-Language
-
Content-Type(仅限于
text/plain
,multipart/form-data
,application/x-www-form-urlencoded
)
-
请求流程:
http
GET /data HTTP/1.1 Origin: http://localhost:3000 HTTP/1.1 200 OK Access-Control-Allow-Origin: http://localhost:3000
预检请求(Preflight Request)场景:
当请求不符合简单请求条件时,浏览器自动发起 OPTIONS 预检请求:
http
OPTIONS /data HTTP/1.1 Origin: http://localhost:3000 Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header HTTP/1.1 204 No Content Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Max-Age: 86400
2.2 关键响应头解析
响应头 | 作用说明 | 示例值 |
---|---|---|
Access-Control-Allow-Origin | 允许访问的源(* 表示允许任意源) | https://example.com |
Access-Control-Allow-Methods | 允许的 HTTP 方法 | GET, POST, PUT |
Access-Control-Allow-Headers | 允许的自定义请求头 | X-API-Key, Content-Type |
Access-Control-Expose-Headers | 允许前端访问的响应头 | X-Total-Count |
Access-Control-Max-Age | 预检请求缓存时间(秒) | 3600 |
Access-Control-Allow-Credentials | 是否允许发送 Cookie | true |
第三章:Java 服务端的 CORS 实现
3.1 Servlet 过滤器方案
对于传统 Servlet 应用,可通过自定义 Filter 实现:
java
public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "https://trusted-domain.com"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(req, res); } }
3.2 Spring Framework 配置方案
Spring 提供了更优雅的配置方式:
注解方式(适用于单个控制器):
java
@RestController @CrossOrigin(origins = "https://trusted-domain.com", allowedHeaders = "*", allowCredentials = "true") public class ApiController { // ... }
全局配置(推荐方式):
java
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("https://trusted-domain.com") .allowedMethods("GET", "POST", "PUT") .allowedHeaders("*") .exposedHeaders("X-Total-Count") .allowCredentials(true) .maxAge(3600); } }
3.3 Spring Security 集成注意事项
当同时使用 Spring Security 时,需确保 CORS 配置在安全过滤器之前:
java
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and() // 其他安全配置... } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://trusted-domain.com")); config.setAllowedMethods(List.of("GET", "POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }
第四章:CORS 安全风险与防护实践
4.1 高风险配置反模式
危险实践 1:开放全部来源
java
// 绝对禁止的配置! .setAllowedOrigins("*")
此时攻击者可以在任意网站发起针对你 API 的 CSRF 攻击
危险实践 2:信任动态 Origin
java
// 从请求头中反射设置 Origin response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
未经验证的 Origin 可能导致任意网站获得跨域访问权限
4.2 凭证泄露风险
当同时启用以下配置时:
java
.allowCredentials(true) .allowedOrigins("*")
浏览器将拒绝此配置,因为携带 Cookie 的跨域请求不允许使用通配符 Origin
4.3 防御性配置策略
-
白名单校验:通过正则表达式严格校验 Origin
java
String origin = request.getHeader("Origin"); if (isValidOrigin(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); }
-
最小权限原则:
java
.allowedMethods("GET", "POST") // 仅开放必要方法 .exposedHeaders("Content-Type") // 仅暴露必要响应头
-
生产环境禁用敏感头:
http
Access-Control-Expose-Headers: Authorization, X-Secret-Token // 高危!
-
结合 CSRF 防护:
java
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
第五章:实战调试技巧
5.1 浏览器开发者工具分析
-
Network 标签:查看请求的
Origin
和响应头是否符合预期 -
控制台警告:捕获 CORS 错误信息,如:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ...
5.2 CURL 模拟预检请求
bash
curl -X OPTIONS \ -H "Origin: http://untrusted-site.com" \ -H "Access-Control-Request-Method: DELETE" \ -H "Access-Control-Request-Headers: X-Custom-Header" \ -I https://api.example.com/data
5.3 服务端日志诊断
在 Filter 中记录 CORS 相关头信息:
java
System.out.println("Received Origin: " + request.getHeader("Origin"));
结语
正确理解和配置 CORS 是 Java 开发者保障 Web 应用安全的重要环节。通过本文的深度解析,我们不仅掌握了如何在 Spring 等主流框架中实现跨域支持,更重要的是建立了安全配置的防御性思维。记住:永远不要盲目开放 *
通配符,始终对 Origin 保持怀疑,将最小权限原则贯穿于整个配置过程。只有将便捷性与安全性有机结合,才能构建出既灵活又可靠的现代化 Web 服务。