1. 项目概述从“跨域错误”到CORS配置相信不少Java后端开发者尤其是刚接触前后端分离项目时都遇到过那个经典的浏览器控制台错误Access to fetch at ‘http://api.example.com‘ from origin ‘http://localhost:8080‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.。这个令人头疼的“跨域”问题几乎是现代Web开发的必经之路。它并非服务器拒绝响应而是浏览器出于安全考虑主动拦截了来自不同源的响应防止恶意网站窃取数据。我们今天要深入探讨的就是在Java生态中如何正确、优雅且安全地配置CORS跨域资源共享让前端应用能顺利调用后端API。CORS本身是一套W3C标准它允许服务器声明哪些“外域”有权访问自己的资源。对于Java开发者而言实现CORS不仅仅是加几个响应头那么简单。它涉及到对不同HTTP方法的处理特别是OPTIONS预检请求、对携带凭证如Cookie请求的支持以及如何在便捷与安全之间找到平衡点。一个配置不当的CORS策略轻则导致功能异常重则可能引入严重的安全漏洞例如将内部API暴露给任意网站。因此理解其原理并掌握在Spring Boot、Servlet Filter等常见场景下的配置方法是每一位Java Web开发者必须掌握的技能。接下来我将结合多年项目实战经验为你拆解从基础配置到高级安全考量的完整方案。2. CORS核心原理与工作机制拆解在动手写代码之前我们必须先搞清楚浏览器和服务器之间到底发生了什么。很多开发者尝试配置CORS失败根源在于对机制理解不透彻。CORS的交互主要分为两种场景简单请求和预检请求。理解它们的区别是成功配置的第一步。2.1 简单请求与非简单请求的判定浏览器并非对所有跨域请求都“一视同仁”。为了效率它定义了一类“简单请求”这类请求可以直接发出服务器通过响应头来控制是否允许跨域。一个请求必须同时满足以下所有条件才是简单请求方法限制仅限GET、HEAD、POST。请求头限制除了浏览器自动设置的头部如Connection、User-Agent等只能包含以下安全列表中的头部Accept、Accept-Language、Content-Language、Content-Type但值仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain。请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器。如果你的请求使用了PUT、DELETE方法或者Content-Type是application/json或者你自定义了Authorization、X-Requested-With等头部那么这个请求就属于非简单请求。对于简单请求浏览器会直接发出请求并在响应中检查是否包含Access-Control-Allow-Origin头部。如果包含且值匹配请求源或为*则允许跨域访问响应数据。2.2 预检请求的完整流程对于非简单请求浏览器不会直接发出实际请求比如你的POST /api/user。它会先自动发起一个OPTIONS方法的预检请求到同一个URL。这个预检请求的目的是“询问”服务器“我来自http://localhost:8080想用POST方法携带Content-Type: application/json和Authorization头你允许吗”预检请求会携带几个关键头部Origin: 请求来源。Access-Control-Request-Method: 实际请求将要使用的方法如POST。Access-Control-Request-Headers: 实际请求将要携带的自定义头部列表如authorization, content-type。服务器必须正确响应这个OPTIONS请求。响应中需要包含Access-Control-Allow-Origin: 允许的源。Access-Control-Allow-Methods: 允许的方法列表。Access-Control-Allow-Headers: 允许的请求头列表。Access-Control-Max-Age: 可选指定预检请求的结果可以被缓存多久秒减少后续请求的预检次数。只有预检请求的响应通过了浏览器的检查浏览器才会接着发出真正的实际请求。如果预检请求失败比如服务器没处理OPTIONS或者返回的允许头/方法不匹配你会看到那个经典的has been blocked by CORS policy: Response to preflight request doesn‘t pass access control check错误而你的实际请求代码甚至不会被执行。注意很多新手配置时只关注了实际请求的响应头却完全忽略了处理OPTIONS预检请求这是导致配置失败的最常见原因。你的后端应用必须能正确处理OPTIONS方法的请求并返回正确的CORS头部。3. Spring Boot中的CORS配置方案详解Spring Boot作为Java生态的绝对主流提供了多种灵活配置CORS的方式。从全局配置到细粒度控制我们需要根据项目需求选择合适的方法。3.1 使用CrossOrigin注解进行控制器级别配置这是最快速、最直观的方式适合对单个或某几个控制器进行跨域配置。你可以将注解加在控制器类上或具体的方法上。RestController RequestMapping(/api) // 在类上配置对该控制器所有方法生效 CrossOrigin(origins http://localhost:8080, maxAge 3600) public class UserController { GetMapping(/user/{id}) // 方法上的注解会覆盖类上的配置 CrossOrigin(origins {http://localhost:8080, https://admin.example.com}) public User getUser(PathVariable Long id) { // ... } PostMapping(/user) // 允许所有源生产环境慎用 CrossOrigin(origins *) public User createUser(RequestBody User user) { // ... } }参数解析与实操心得origins/value: 允许的源列表。强烈不建议在生产环境使用*这等同于完全开放存在安全风险。应明确列出前端应用的部署地址。allowedHeaders: 允许的请求头。如果你的前端请求会携带Authorization、X-Token等自定义头必须在这里列出。默认已包含Origin,Accept,Content-Type等简单头部。exposedHeaders: 除了CORS安全响应头Cache-Control,Content-Language等之外你希望浏览器能访问到的额外响应头。例如如果你的API返回一个自定义分页头X-Total-Count就需要在这里暴露。methods: 允许的HTTP方法默认是注解所映射的方法如GetMapping对应GET。allowCredentials: 布尔值是否允许发送Cookie等凭证信息。如果设置为true则origins不能为*必须指定明确的域名否则浏览器会拒绝请求。maxAge: 预检请求缓存时间单位秒。设置一个合理的值如1800可以减少不必要的OPTIONS请求提升性能。踩坑记录我曾在一个项目中将allowCredentials设为true但origins配置了*结果前端始终无法携带Cookie。排查了很久才发现是浏览器遵循规范拒绝了这种不安全的组合。务必记住这个规则。3.2 实现WebMvcConfigurer进行全局配置当你的项目中有大量API需要统一的CORS策略时在每一个控制器上添加注解就显得非常冗余。此时实现WebMvcConfigurer接口并重写addCorsMappings方法是更优雅的全局解决方案。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 匹配的路径模式 .allowedOrigins(http://localhost:8080, https://your-frontend.com) // 允许的源 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS, HEAD) // 允许的方法 .allowedHeaders(*) // 允许所有请求头生产环境建议明确列出 .exposedHeaders(X-Total-Count, X-Custom-Token) // 暴露的响应头 .allowCredentials(true) // 允许凭证 .maxAge(3600); // 预检缓存1小时 // 可以为不同的路径配置不同的策略 registry.addMapping(/public/**) .allowedOrigins(*) .allowedMethods(GET, HEAD) .allowCredentials(false); } }全局配置的优势与注意事项优势配置集中管理方便避免了注解的分散。对于RESTful API项目这通常是首选。注意路径匹配addMapping中的Ant风格路径模式如/api/**要能覆盖所有需要跨域的接口。小心不要遗漏某些路径。allowedHeaders(“*”)的风险虽然方便但意味着接受任何请求头。在安全要求高的场景应像allowedOrigins一样明确列出需要的头部如“Authorization“, “Content-Type“, “X-Requested-With“。与CrossOrigin的优先级如果同时使用了全局配置和CrossOrigin注解注解的配置会覆盖全局配置。这可以用于为特定接口设置更宽松或更严格的策略。3.3 使用CorsFilter进行更底层的控制WebMvcConfigurer的方式在大多数Spring MVC场景下工作良好。但在一些更复杂的情况下比如需要处理静态资源、或者与Spring Security集成时遇到优先级问题或者你使用的是Spring WebFlux响应式编程直接配置一个CorsFilterBean可能是更可靠的选择。Configuration public class CorsConfiguration { Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); // 配置策略 config.setAllowCredentials(true); // 允许凭证 config.addAllowedOrigin(http://localhost:8080); // 注意不能同时使用setAllowCredentials(true)和addAllowedOrigin(*) config.addAllowedOrigin(https://your-frontend.com); config.addAllowedHeader(*); // 允许所有头 config.addAllowedMethod(*); // 允许所有方法 config.setMaxAge(3600L); // 缓存时间 config.addExposedHeader(X-Total-Count); // 暴露自定义头 // 注册CORS配置应用到所有路径 source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }为什么选择CorsFilter优先级更高CorsFilter是一个Servlet Filter它在请求处理链的早期执行能确保在所有Spring MVC处理之前就处理好CORS头部避免与其他Filter如Spring Security的过滤器链冲突。更广泛的适用性它对所有经过Servlet容器的请求都生效包括静态资源请求、错误页面等而WebMvcConfigurer主要作用于DispatcherServlet分发的请求。与Spring Security集成当项目使用Spring Security时有时CORS配置会失效因为Security的过滤器链可能先于CORS逻辑执行并返回了403错误。此时将CorsFilter的Bean定义放在Security配置类之前或者明确在Security配置中调用.cors()并提供一个CorsConfigurationSource是更稳妥的做法。一个与Spring Security集成的常见配置示例EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .cors() // 启用CORS支持会从Spring容器中查找名为corsFilter的Bean或CorsConfigurationSource .and() .authorizeRequests() .antMatchers(/api/public/**).permitAll() .anyRequest().authenticated() .and() .csrf().disable(); // 注意在前后端分离且使用Token等无状态认证时通常可以禁用CSRF } // 显式提供CorsConfigurationSource Bean Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(http://localhost:8080)); configuration.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE, OPTIONS)); configuration.setAllowedHeaders(Arrays.asList(Authorization, Content-Type, X-Requested-With)); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, configuration); return source; } }4. 传统Servlet/Filter项目中的CORS实现如果你的项目没有使用Spring Boot而是基于原生的Servlet API或更轻量的框架如Jersey、Spark等你需要手动编写Filter来处理CORS。这是理解CORS机制最直接的方式。4.1 手动实现CORS Filter下面是一个功能完整的CORS Filter实现示例import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class SimpleCorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; // 1. 设置允许的源根据需求动态或静态配置 String allowedOrigin http://localhost:8080; response.setHeader(Access-Control-Allow-Origin, allowedOrigin); // 2. 处理预检请求OPTIONS if (OPTIONS.equalsIgnoreCase(request.getMethod())) { // 允许的方法 response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); // 允许的头部 response.setHeader(Access-Control-Allow-Headers, Authorization, Content-Type, X-Requested-With); // 允许携带凭证 response.setHeader(Access-Control-Allow-Credentials, true); // 预检请求缓存时间 response.setHeader(Access-Control-Max-Age, 3600); // 预检请求直接返回成功无需继续执行过滤器链 response.setStatus(HttpServletResponse.SC_OK); return; } // 3. 对于非OPTIONS请求继续设置其他CORS头并传递请求 response.setHeader(Access-Control-Allow-Credentials, true); // 可以暴露自定义响应头 response.setHeader(Access-Control-Expose-Headers, X-Custom-Header); // 4. 继续执行过滤器链 chain.doFilter(request, response); } Override public void init(FilterConfig filterConfig) {} Override public void destroy() {} }关键点解析区分请求类型核心逻辑在于判断请求方法是否为OPTIONS。如果是则直接构造一个包含所有必要CORS头部的成功响应并返回不再调用chain.doFilter()因为预检请求不需要执行实际的业务逻辑。Access-Control-Allow-Credentials当需要前端传递Cookie或Authorization头进行认证时此头必须设置为true且Access-Control-Allow-Origin不能为*。Access-Control-Expose-Headers默认情况下浏览器只能访问CORS安全列表中的响应头。如果你在响应中设置了自定义头如分页信息X-Total-Count需要在此列出前端JavaScript才能通过getResponseHeader()读取。4.2 在web.xml中注册Filter编写好Filter后需要在web.xml中配置其映射关系确保它能拦截到所有需要跨域处理的请求。web-app filter filter-nameSimpleCorsFilter/filter-name filter-classcom.yourpackage.SimpleCorsFilter/filter-class /filter filter-mapping filter-nameSimpleCorsFilter/filter-name url-pattern/api/*/url-pattern !-- 只拦截API路径 -- !-- 或者拦截所有请求url-pattern/*/url-pattern -- /filter-mapping /web-app部署与调试心得手动实现Filter时最容易出错的地方就是响应头的设置不全或逻辑错误。务必使用浏览器的开发者工具Network标签仔细检查预检请求OPTIONS和实际请求的请求头和响应头是否完全匹配。特别是Access-Control-Allow-Headers一定要包含前端实际发送的所有自定义头名称大小写敏感。5. CORS配置中的关键安全考量与最佳实践配置CORS不仅仅是为了让前端能访问到API更重要的是在开放访问的同时筑起一道安全防线。一个Access-Control-Allow-Origin: *的配置可能就是你系统最大的漏洞之一。5.1 源Origin白名单严禁使用通配符“*”这是CORS安全中最重要的一条原则。Access-Control-Allow-Origin: *意味着任何网站都可以通过浏览器脚本访问你的API。如果你的API涉及用户数据、管理功能或任何敏感操作这等同于完全不设防。正确做法建立严格的白名单机制。Spring Boot示例动态白名单可以从数据库或配置中心读取允许的域名列表在CorsConfigurationSource或Filter中动态判断。Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); config.setAllowCredentials(true); // 假设从环境变量或配置类中获取白名单 ListString allowedOrigins Arrays.asList(env.getProperty(cors.allowed.origins, ).split(,)); config.setAllowedOrigins(allowedOrigins); // ... 其他配置 source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }生产环境建议将白名单配置在外部配置文件如application-prod.yml或环境变量中便于不同环境开发、测试、生产独立管理。5.2 限制允许的HTTP方法与请求头不要盲目地允许所有方法*和所有请求头*。应根据API的实际功能遵循最小权限原则。方法限制一个只读的查询API只允许GET和HEAD即可。请求头限制明确列出前端应用会使用的头部如Authorization,Content-Type,X-Requested-With。这可以防止攻击者利用未经验证的自定义头进行攻击。5.3 谨慎处理凭证与敏感信息当Access-Control-Allow-Credentials: true时浏览器会将Cookie等凭证信息随请求发送。这在与基于Session的认证结合时很常见。但请务必注意Origin不能为*这是浏览器的强制规定。响应中的敏感信息即使通过了CORS检查也要确保API本身有完善的权限验证如检查Session或Token防止越权访问。CORS不是身份验证或授权机制它只是一个“通道”开关。避免信息泄露检查通过CORS暴露的响应头Access-Control-Expose-Headers是否包含敏感信息如Server内部版本、X-Powered-By等。5.4 合理设置Access-Control-Max-Age对于频繁发生的非简单请求设置一个合理的Access-Control-Max-Age例如1800秒即30分钟可以显著减少预检请求的数量提升性能。但也不宜设置过长以免在策略变更时浏览器因缓存旧策略而出现访问问题。6. 常见问题排查与调试技巧实录即使按照指南配置在实际开发中依然会遇到各种奇怪的CORS问题。下面是我在多年支持项目中总结的排查清单和调试技巧。6.1 问题排查速查表现象可能原因解决方案控制台报错No ‘Access-Control-Allow-Origin‘ header1. 服务器未配置任何CORS响应头。2. 配置的Filter或Interceptor路径未覆盖当前请求URL。3. 服务器内部发生错误5xx未走到设置CORS头的代码。1. 检查服务器端CORS配置是否生效。2. 检查请求URL是否匹配配置的路径模式。3. 查看服务器日志确认业务代码或过滤器链中是否有异常导致请求提前返回。控制台报错Response to preflight request doesn‘t pass...1. 服务器未正确处理OPTIONS预检请求返回了404或405。2. 预检请求的响应中Access-Control-Allow-Methods或Access-Control-Allow-Headers不包含实际请求使用的方法或头。1. 确保你的CORS配置能处理OPTIONS方法。在Spring中全局配置或CorsFilter会自动处理。2. 仔细比对浏览器预检请求的Access-Control-Request-Method和Access-Control-Request-Headers与服务器响应的允许值是否完全匹配大小写敏感。控制台报错The value of the ‘Access-Control-Allow-Origin‘ header... must not be the wildcard ‘*‘前端请求设置了withCredentials: true如Axios的axios.defaults.withCredentials true但服务器响应头Access-Control-Allow-Origin为*。将服务器配置中的Access-Control-Allow-Origin改为明确的前端源地址并与Access-Control-Allow-Credentials: true配合使用。前端能收到响应但JavaScript读不到自定义响应头服务器未在Access-Control-Expose-Headers响应头中暴露该自定义头。在服务器CORS配置中添加exposedHeaders包含你需要让前端访问的头部名称。使用了Spring Security后CORS失效Spring Security的过滤器链可能在CORS过滤器之前执行并因为认证失败而返回了403/401导致CORS头未被添加。1. 确保CorsFilter的优先级高于Spring Security的过滤器。可以通过Order注解或Bean定义顺序控制。2.推荐在Spring Security配置中显式启用CORS.cors()并配置CorsConfigurationSource如上文3.3节所示。6.2 浏览器开发者工具调试指南浏览器开发者工具F12的Network标签是调试CORS问题最强大的武器。找到出错的请求红色标记的请求通常就是被CORS策略阻止的请求。查看请求详情点击该请求在Headers标签页中查看Request Headers里的Origin确认来源。对于非简单请求查看是否有Access-Control-Request-Method和Access-Control-Request-Headers确认这是预检请求。查看响应详情重点查看Response Headers是否有Access-Control-Allow-Origin值是否匹配请求的Origin对于预检请求是否有Access-Control-Allow-Methods和Access-Control-Allow-Headers是否包含了实际请求所需的方法和头如果需要凭证是否有Access-Control-Allow-Credentials: true对比与验证将浏览器的请求头与服务器的响应头逐条对比不一致的地方就是问题所在。6.3 服务器端日志排查如果浏览器端信息不全或者怀疑请求根本没到你的应用层就需要查看服务器日志。检查请求是否到达在CORS Filter或拦截器中打印日志确认OPTIONS和实际请求是否被正确处理。检查异常堆栈查看是否有其他过滤器或业务逻辑抛出异常导致请求在设置CORS头之前就返回了错误响应。使用工具测试用Postman或curl直接发送请求不带Origin头可以绕过浏览器CORS检查直接测试API本身是否工作正常从而隔离问题是出在API功能还是CORS配置上。最后记住一个核心原则CORS是浏览器的安全策略。如果你的移动端App或桌面客户端直接调用API是不会触发CORS限制的。这有助于你在排查时确定问题的边界。配置CORS就像为你的API开一扇可控的门既要方便合法的访问者进出又要牢牢锁住非法的闯入者。理解机制、细致配置、严格白名单是做好这件事的关键。
Java后端CORS跨域配置实战:从原理到Spring Boot安全实践
1. 项目概述从“跨域错误”到CORS配置相信不少Java后端开发者尤其是刚接触前后端分离项目时都遇到过那个经典的浏览器控制台错误Access to fetch at ‘http://api.example.com‘ from origin ‘http://localhost:8080‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.。这个令人头疼的“跨域”问题几乎是现代Web开发的必经之路。它并非服务器拒绝响应而是浏览器出于安全考虑主动拦截了来自不同源的响应防止恶意网站窃取数据。我们今天要深入探讨的就是在Java生态中如何正确、优雅且安全地配置CORS跨域资源共享让前端应用能顺利调用后端API。CORS本身是一套W3C标准它允许服务器声明哪些“外域”有权访问自己的资源。对于Java开发者而言实现CORS不仅仅是加几个响应头那么简单。它涉及到对不同HTTP方法的处理特别是OPTIONS预检请求、对携带凭证如Cookie请求的支持以及如何在便捷与安全之间找到平衡点。一个配置不当的CORS策略轻则导致功能异常重则可能引入严重的安全漏洞例如将内部API暴露给任意网站。因此理解其原理并掌握在Spring Boot、Servlet Filter等常见场景下的配置方法是每一位Java Web开发者必须掌握的技能。接下来我将结合多年项目实战经验为你拆解从基础配置到高级安全考量的完整方案。2. CORS核心原理与工作机制拆解在动手写代码之前我们必须先搞清楚浏览器和服务器之间到底发生了什么。很多开发者尝试配置CORS失败根源在于对机制理解不透彻。CORS的交互主要分为两种场景简单请求和预检请求。理解它们的区别是成功配置的第一步。2.1 简单请求与非简单请求的判定浏览器并非对所有跨域请求都“一视同仁”。为了效率它定义了一类“简单请求”这类请求可以直接发出服务器通过响应头来控制是否允许跨域。一个请求必须同时满足以下所有条件才是简单请求方法限制仅限GET、HEAD、POST。请求头限制除了浏览器自动设置的头部如Connection、User-Agent等只能包含以下安全列表中的头部Accept、Accept-Language、Content-Language、Content-Type但值仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain。请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器。如果你的请求使用了PUT、DELETE方法或者Content-Type是application/json或者你自定义了Authorization、X-Requested-With等头部那么这个请求就属于非简单请求。对于简单请求浏览器会直接发出请求并在响应中检查是否包含Access-Control-Allow-Origin头部。如果包含且值匹配请求源或为*则允许跨域访问响应数据。2.2 预检请求的完整流程对于非简单请求浏览器不会直接发出实际请求比如你的POST /api/user。它会先自动发起一个OPTIONS方法的预检请求到同一个URL。这个预检请求的目的是“询问”服务器“我来自http://localhost:8080想用POST方法携带Content-Type: application/json和Authorization头你允许吗”预检请求会携带几个关键头部Origin: 请求来源。Access-Control-Request-Method: 实际请求将要使用的方法如POST。Access-Control-Request-Headers: 实际请求将要携带的自定义头部列表如authorization, content-type。服务器必须正确响应这个OPTIONS请求。响应中需要包含Access-Control-Allow-Origin: 允许的源。Access-Control-Allow-Methods: 允许的方法列表。Access-Control-Allow-Headers: 允许的请求头列表。Access-Control-Max-Age: 可选指定预检请求的结果可以被缓存多久秒减少后续请求的预检次数。只有预检请求的响应通过了浏览器的检查浏览器才会接着发出真正的实际请求。如果预检请求失败比如服务器没处理OPTIONS或者返回的允许头/方法不匹配你会看到那个经典的has been blocked by CORS policy: Response to preflight request doesn‘t pass access control check错误而你的实际请求代码甚至不会被执行。注意很多新手配置时只关注了实际请求的响应头却完全忽略了处理OPTIONS预检请求这是导致配置失败的最常见原因。你的后端应用必须能正确处理OPTIONS方法的请求并返回正确的CORS头部。3. Spring Boot中的CORS配置方案详解Spring Boot作为Java生态的绝对主流提供了多种灵活配置CORS的方式。从全局配置到细粒度控制我们需要根据项目需求选择合适的方法。3.1 使用CrossOrigin注解进行控制器级别配置这是最快速、最直观的方式适合对单个或某几个控制器进行跨域配置。你可以将注解加在控制器类上或具体的方法上。RestController RequestMapping(/api) // 在类上配置对该控制器所有方法生效 CrossOrigin(origins http://localhost:8080, maxAge 3600) public class UserController { GetMapping(/user/{id}) // 方法上的注解会覆盖类上的配置 CrossOrigin(origins {http://localhost:8080, https://admin.example.com}) public User getUser(PathVariable Long id) { // ... } PostMapping(/user) // 允许所有源生产环境慎用 CrossOrigin(origins *) public User createUser(RequestBody User user) { // ... } }参数解析与实操心得origins/value: 允许的源列表。强烈不建议在生产环境使用*这等同于完全开放存在安全风险。应明确列出前端应用的部署地址。allowedHeaders: 允许的请求头。如果你的前端请求会携带Authorization、X-Token等自定义头必须在这里列出。默认已包含Origin,Accept,Content-Type等简单头部。exposedHeaders: 除了CORS安全响应头Cache-Control,Content-Language等之外你希望浏览器能访问到的额外响应头。例如如果你的API返回一个自定义分页头X-Total-Count就需要在这里暴露。methods: 允许的HTTP方法默认是注解所映射的方法如GetMapping对应GET。allowCredentials: 布尔值是否允许发送Cookie等凭证信息。如果设置为true则origins不能为*必须指定明确的域名否则浏览器会拒绝请求。maxAge: 预检请求缓存时间单位秒。设置一个合理的值如1800可以减少不必要的OPTIONS请求提升性能。踩坑记录我曾在一个项目中将allowCredentials设为true但origins配置了*结果前端始终无法携带Cookie。排查了很久才发现是浏览器遵循规范拒绝了这种不安全的组合。务必记住这个规则。3.2 实现WebMvcConfigurer进行全局配置当你的项目中有大量API需要统一的CORS策略时在每一个控制器上添加注解就显得非常冗余。此时实现WebMvcConfigurer接口并重写addCorsMappings方法是更优雅的全局解决方案。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 匹配的路径模式 .allowedOrigins(http://localhost:8080, https://your-frontend.com) // 允许的源 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS, HEAD) // 允许的方法 .allowedHeaders(*) // 允许所有请求头生产环境建议明确列出 .exposedHeaders(X-Total-Count, X-Custom-Token) // 暴露的响应头 .allowCredentials(true) // 允许凭证 .maxAge(3600); // 预检缓存1小时 // 可以为不同的路径配置不同的策略 registry.addMapping(/public/**) .allowedOrigins(*) .allowedMethods(GET, HEAD) .allowCredentials(false); } }全局配置的优势与注意事项优势配置集中管理方便避免了注解的分散。对于RESTful API项目这通常是首选。注意路径匹配addMapping中的Ant风格路径模式如/api/**要能覆盖所有需要跨域的接口。小心不要遗漏某些路径。allowedHeaders(“*”)的风险虽然方便但意味着接受任何请求头。在安全要求高的场景应像allowedOrigins一样明确列出需要的头部如“Authorization“, “Content-Type“, “X-Requested-With“。与CrossOrigin的优先级如果同时使用了全局配置和CrossOrigin注解注解的配置会覆盖全局配置。这可以用于为特定接口设置更宽松或更严格的策略。3.3 使用CorsFilter进行更底层的控制WebMvcConfigurer的方式在大多数Spring MVC场景下工作良好。但在一些更复杂的情况下比如需要处理静态资源、或者与Spring Security集成时遇到优先级问题或者你使用的是Spring WebFlux响应式编程直接配置一个CorsFilterBean可能是更可靠的选择。Configuration public class CorsConfiguration { Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); // 配置策略 config.setAllowCredentials(true); // 允许凭证 config.addAllowedOrigin(http://localhost:8080); // 注意不能同时使用setAllowCredentials(true)和addAllowedOrigin(*) config.addAllowedOrigin(https://your-frontend.com); config.addAllowedHeader(*); // 允许所有头 config.addAllowedMethod(*); // 允许所有方法 config.setMaxAge(3600L); // 缓存时间 config.addExposedHeader(X-Total-Count); // 暴露自定义头 // 注册CORS配置应用到所有路径 source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }为什么选择CorsFilter优先级更高CorsFilter是一个Servlet Filter它在请求处理链的早期执行能确保在所有Spring MVC处理之前就处理好CORS头部避免与其他Filter如Spring Security的过滤器链冲突。更广泛的适用性它对所有经过Servlet容器的请求都生效包括静态资源请求、错误页面等而WebMvcConfigurer主要作用于DispatcherServlet分发的请求。与Spring Security集成当项目使用Spring Security时有时CORS配置会失效因为Security的过滤器链可能先于CORS逻辑执行并返回了403错误。此时将CorsFilter的Bean定义放在Security配置类之前或者明确在Security配置中调用.cors()并提供一个CorsConfigurationSource是更稳妥的做法。一个与Spring Security集成的常见配置示例EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .cors() // 启用CORS支持会从Spring容器中查找名为corsFilter的Bean或CorsConfigurationSource .and() .authorizeRequests() .antMatchers(/api/public/**).permitAll() .anyRequest().authenticated() .and() .csrf().disable(); // 注意在前后端分离且使用Token等无状态认证时通常可以禁用CSRF } // 显式提供CorsConfigurationSource Bean Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(http://localhost:8080)); configuration.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE, OPTIONS)); configuration.setAllowedHeaders(Arrays.asList(Authorization, Content-Type, X-Requested-With)); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, configuration); return source; } }4. 传统Servlet/Filter项目中的CORS实现如果你的项目没有使用Spring Boot而是基于原生的Servlet API或更轻量的框架如Jersey、Spark等你需要手动编写Filter来处理CORS。这是理解CORS机制最直接的方式。4.1 手动实现CORS Filter下面是一个功能完整的CORS Filter实现示例import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class SimpleCorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; // 1. 设置允许的源根据需求动态或静态配置 String allowedOrigin http://localhost:8080; response.setHeader(Access-Control-Allow-Origin, allowedOrigin); // 2. 处理预检请求OPTIONS if (OPTIONS.equalsIgnoreCase(request.getMethod())) { // 允许的方法 response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); // 允许的头部 response.setHeader(Access-Control-Allow-Headers, Authorization, Content-Type, X-Requested-With); // 允许携带凭证 response.setHeader(Access-Control-Allow-Credentials, true); // 预检请求缓存时间 response.setHeader(Access-Control-Max-Age, 3600); // 预检请求直接返回成功无需继续执行过滤器链 response.setStatus(HttpServletResponse.SC_OK); return; } // 3. 对于非OPTIONS请求继续设置其他CORS头并传递请求 response.setHeader(Access-Control-Allow-Credentials, true); // 可以暴露自定义响应头 response.setHeader(Access-Control-Expose-Headers, X-Custom-Header); // 4. 继续执行过滤器链 chain.doFilter(request, response); } Override public void init(FilterConfig filterConfig) {} Override public void destroy() {} }关键点解析区分请求类型核心逻辑在于判断请求方法是否为OPTIONS。如果是则直接构造一个包含所有必要CORS头部的成功响应并返回不再调用chain.doFilter()因为预检请求不需要执行实际的业务逻辑。Access-Control-Allow-Credentials当需要前端传递Cookie或Authorization头进行认证时此头必须设置为true且Access-Control-Allow-Origin不能为*。Access-Control-Expose-Headers默认情况下浏览器只能访问CORS安全列表中的响应头。如果你在响应中设置了自定义头如分页信息X-Total-Count需要在此列出前端JavaScript才能通过getResponseHeader()读取。4.2 在web.xml中注册Filter编写好Filter后需要在web.xml中配置其映射关系确保它能拦截到所有需要跨域处理的请求。web-app filter filter-nameSimpleCorsFilter/filter-name filter-classcom.yourpackage.SimpleCorsFilter/filter-class /filter filter-mapping filter-nameSimpleCorsFilter/filter-name url-pattern/api/*/url-pattern !-- 只拦截API路径 -- !-- 或者拦截所有请求url-pattern/*/url-pattern -- /filter-mapping /web-app部署与调试心得手动实现Filter时最容易出错的地方就是响应头的设置不全或逻辑错误。务必使用浏览器的开发者工具Network标签仔细检查预检请求OPTIONS和实际请求的请求头和响应头是否完全匹配。特别是Access-Control-Allow-Headers一定要包含前端实际发送的所有自定义头名称大小写敏感。5. CORS配置中的关键安全考量与最佳实践配置CORS不仅仅是为了让前端能访问到API更重要的是在开放访问的同时筑起一道安全防线。一个Access-Control-Allow-Origin: *的配置可能就是你系统最大的漏洞之一。5.1 源Origin白名单严禁使用通配符“*”这是CORS安全中最重要的一条原则。Access-Control-Allow-Origin: *意味着任何网站都可以通过浏览器脚本访问你的API。如果你的API涉及用户数据、管理功能或任何敏感操作这等同于完全不设防。正确做法建立严格的白名单机制。Spring Boot示例动态白名单可以从数据库或配置中心读取允许的域名列表在CorsConfigurationSource或Filter中动态判断。Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); config.setAllowCredentials(true); // 假设从环境变量或配置类中获取白名单 ListString allowedOrigins Arrays.asList(env.getProperty(cors.allowed.origins, ).split(,)); config.setAllowedOrigins(allowedOrigins); // ... 其他配置 source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }生产环境建议将白名单配置在外部配置文件如application-prod.yml或环境变量中便于不同环境开发、测试、生产独立管理。5.2 限制允许的HTTP方法与请求头不要盲目地允许所有方法*和所有请求头*。应根据API的实际功能遵循最小权限原则。方法限制一个只读的查询API只允许GET和HEAD即可。请求头限制明确列出前端应用会使用的头部如Authorization,Content-Type,X-Requested-With。这可以防止攻击者利用未经验证的自定义头进行攻击。5.3 谨慎处理凭证与敏感信息当Access-Control-Allow-Credentials: true时浏览器会将Cookie等凭证信息随请求发送。这在与基于Session的认证结合时很常见。但请务必注意Origin不能为*这是浏览器的强制规定。响应中的敏感信息即使通过了CORS检查也要确保API本身有完善的权限验证如检查Session或Token防止越权访问。CORS不是身份验证或授权机制它只是一个“通道”开关。避免信息泄露检查通过CORS暴露的响应头Access-Control-Expose-Headers是否包含敏感信息如Server内部版本、X-Powered-By等。5.4 合理设置Access-Control-Max-Age对于频繁发生的非简单请求设置一个合理的Access-Control-Max-Age例如1800秒即30分钟可以显著减少预检请求的数量提升性能。但也不宜设置过长以免在策略变更时浏览器因缓存旧策略而出现访问问题。6. 常见问题排查与调试技巧实录即使按照指南配置在实际开发中依然会遇到各种奇怪的CORS问题。下面是我在多年支持项目中总结的排查清单和调试技巧。6.1 问题排查速查表现象可能原因解决方案控制台报错No ‘Access-Control-Allow-Origin‘ header1. 服务器未配置任何CORS响应头。2. 配置的Filter或Interceptor路径未覆盖当前请求URL。3. 服务器内部发生错误5xx未走到设置CORS头的代码。1. 检查服务器端CORS配置是否生效。2. 检查请求URL是否匹配配置的路径模式。3. 查看服务器日志确认业务代码或过滤器链中是否有异常导致请求提前返回。控制台报错Response to preflight request doesn‘t pass...1. 服务器未正确处理OPTIONS预检请求返回了404或405。2. 预检请求的响应中Access-Control-Allow-Methods或Access-Control-Allow-Headers不包含实际请求使用的方法或头。1. 确保你的CORS配置能处理OPTIONS方法。在Spring中全局配置或CorsFilter会自动处理。2. 仔细比对浏览器预检请求的Access-Control-Request-Method和Access-Control-Request-Headers与服务器响应的允许值是否完全匹配大小写敏感。控制台报错The value of the ‘Access-Control-Allow-Origin‘ header... must not be the wildcard ‘*‘前端请求设置了withCredentials: true如Axios的axios.defaults.withCredentials true但服务器响应头Access-Control-Allow-Origin为*。将服务器配置中的Access-Control-Allow-Origin改为明确的前端源地址并与Access-Control-Allow-Credentials: true配合使用。前端能收到响应但JavaScript读不到自定义响应头服务器未在Access-Control-Expose-Headers响应头中暴露该自定义头。在服务器CORS配置中添加exposedHeaders包含你需要让前端访问的头部名称。使用了Spring Security后CORS失效Spring Security的过滤器链可能在CORS过滤器之前执行并因为认证失败而返回了403/401导致CORS头未被添加。1. 确保CorsFilter的优先级高于Spring Security的过滤器。可以通过Order注解或Bean定义顺序控制。2.推荐在Spring Security配置中显式启用CORS.cors()并配置CorsConfigurationSource如上文3.3节所示。6.2 浏览器开发者工具调试指南浏览器开发者工具F12的Network标签是调试CORS问题最强大的武器。找到出错的请求红色标记的请求通常就是被CORS策略阻止的请求。查看请求详情点击该请求在Headers标签页中查看Request Headers里的Origin确认来源。对于非简单请求查看是否有Access-Control-Request-Method和Access-Control-Request-Headers确认这是预检请求。查看响应详情重点查看Response Headers是否有Access-Control-Allow-Origin值是否匹配请求的Origin对于预检请求是否有Access-Control-Allow-Methods和Access-Control-Allow-Headers是否包含了实际请求所需的方法和头如果需要凭证是否有Access-Control-Allow-Credentials: true对比与验证将浏览器的请求头与服务器的响应头逐条对比不一致的地方就是问题所在。6.3 服务器端日志排查如果浏览器端信息不全或者怀疑请求根本没到你的应用层就需要查看服务器日志。检查请求是否到达在CORS Filter或拦截器中打印日志确认OPTIONS和实际请求是否被正确处理。检查异常堆栈查看是否有其他过滤器或业务逻辑抛出异常导致请求在设置CORS头之前就返回了错误响应。使用工具测试用Postman或curl直接发送请求不带Origin头可以绕过浏览器CORS检查直接测试API本身是否工作正常从而隔离问题是出在API功能还是CORS配置上。最后记住一个核心原则CORS是浏览器的安全策略。如果你的移动端App或桌面客户端直接调用API是不会触发CORS限制的。这有助于你在排查时确定问题的边界。配置CORS就像为你的API开一扇可控的门既要方便合法的访问者进出又要牢牢锁住非法的闯入者。理解机制、细致配置、严格白名单是做好这件事的关键。