ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot CORS跨域配置与排错:前后端分离联调指南

Spring Boot CORS跨域配置与排错:前后端分离联调指南 简介Spring Boot 开发者常遇到的跨域问题在这份 PDF 文档中得到系统梳理资源面向 Java Web 开发者和前后端分离项目维护人员讲解 CORS 跨域资源共享机制及其在 Spring Boot 中的落地。文档按两条主线展开一是自定义 CorsFilter重写 OncePerRequestFilter 的 doFilterInternal 方法在响应头中配置 Access-Control-Allow-Origin、Access-Control-Allow-Credentials、Access-Control-Allow-Methods、Access-Control-Max-Age 和 Access-Control-Allow-Headers并处理 OPTIONS 预检请求二是通过 Configuration 定义 CorsConfig结合 UrlBasedCorsConfigurationSource、CorsConfiguration 与 FilterRegistrationBean 完成全局配置可通过 addAllowedOrigin、addAllowedHeader、addAllowedMethod 精确控制允许的域名、请求头和请求方法同时用 setOrder 控制过滤器优先级。PDF 内包含可直接参考的 Filter 实现和配置类代码片段也结合跨域概念解释配置背后的原理并点明两种方式各自的适用场景。包体为 1 个 PDF 文件压缩包约 34KB轻量易读目前已有 2834 人浏览学习。文档还总结了两种方式的优缺点和选型建议适合需要快速排查前后端联调跨域报错、或想在不同项目中灵活选择配置方式的 Java 工程师。1. springboot cors 跨域报错是前后端分离最常见的联调问题springboot cors 跨域报错是前后端分离项目最常见的联调问题页面在 5173 端口接口在 8080 端口前端一个 fetch 就抛 has been blocked by cors policy: no access-control-allow-origin header is present。这个报错和 Spring Boot 本身没关系请求实际到了后端是浏览器读到响应没有 Access-Control-Allow-Origin 头才拒绝放行。springboot 配 cors 的实质是让 MVC 按规则给响应补头、按规范处理 OPTIONS 预检最常见的就是全局配置和 CrossOrigin 注解两种方式。文章按浏览器检查顺序讲参数、allowedOriginPatterns 与 credentials 的坑以及拦截器和 Security 如何破坏配置适合联调被跨域卡住的人也够得着 springboot 面试里 cors 配置错误这道题。2. 预检机制与 6 个响应头配 springboot cors 前先看清浏览器要什么2.1 简单请求与预检请求的分界浏览器不是对所有跨域请求都先发 OPTIONS。满足全部条件的叫简单请求方法落在 GET/HEAD/POSTContent-Type 只能是 application/x-www-form-urlencoded、multipart/form-data、text/plain且没有 authorization 之类的自定义头。满足时浏览器直接发真实请求后端只要在响应里带 Access-Control-Allow-Origin 就能通过。条件任一不满足——接口要求 application/json、调用方带了 Authorization 头、用了 PUT/DELETE——浏览器就先发 OPTIONS 预检请求头里带 Access-Control-Request-Method 和 Access-Control-Request-Headers问我打算这么调你让不让。后端匹配并返回一组 Access-Control-Allow-* 头预检算通过不匹配或没配 cors浏览器直接拦掉真实请求console 里就是那段 blocked 文案。所以排查第一步永远是打开 Network 看有没有 OPTIONS 记录有是预检环节挂了没有是真实响应缺头。2.2 Access-Control-* 响应头参数与手动预检命令六个响应头决定预检和真实请求是否通过它们在 Spring 里的配置入口如下表响应头配置入口作用翻车点Access-Control-Allow-OriginallowedOrigins / allowedOriginPatterns声明允许哪个来源读响应配了*又开 credentialsAccess-Control-Allow-MethodsallowedMethods预检放行的 HTTP 方法漏掉 PUT/DELETEAccess-Control-Allow-HeadersallowedHeaders预检放行的自定义请求头漏掉 authorizationAccess-Control-Allow-CredentialsallowCredentials是否允许带 cookie 凭据与*冲突Access-Control-Expose-HeadersexposedHeaders前端 JS 能读到的响应头白名单漏掉 X-Total-CountAccess-Control-Max-AgemaxAge预检结果在浏览器缓存秒数设太大导致改配置不生效# 在本地复现浏览器预检路径换成实际接口 curl -i -X OPTIONS http://localhost:8080/api/v1/order/1 \ -H Origin: http://localhost:5173 \ -H Access-Control-Request-Method: GET \ -H Access-Control-Request-Headers: authorization命令说明-i打印响应头三组-H是浏览器发预检时原样带上的内容后端返回的 Access-Control-Allow-Origin 等于http://localhost:5173说明 MVC 的 cors 开关已开。参数说明Origin 必须和页面实际域名端口完全一致带不带 authorization 那句决定 Access-Control-Request-Headers 是否出现业务里用了 token 就一定会有。2.3 Spring 家族里 cors 生效的两层位置第一层在 DispatcherServlet 内部由 AbstractHandlerMapping 负责。它按路径找到 HandlerExecutionChain 后把合并好的 CorsConfiguration 包装成 CorsInterceptor 塞进执行链预检请求在这一步由 DefaultCorsProcessor 直接写响应不会进入 Controller。addCorsMappings 写的全局配置和 CrossOrigin 注解都注入到这一层。第二层是 CorsFilter一个普通 Servlet Filter跑在 DispatcherServlet 之前不依赖路径匹配Spring Security 和从旧 springmvc 工程改造过来的 Filter 链里都能用。Spring Boot 自动配置原理里有对应的 CorsAutoConfiguration项目没定义 CorsFilter Bean、但写了 spring.web.cors.* 属性时它会自动装配一个 CorsFilter。大多数人不会走属性文件这条路因为写不了复杂规则但对springmvc 工程如何改造成 springboot 工程的老项目这个自动装配常常是重复响应头的来源。提示同一个请求可能被两层同时处理。Filter 补一次头HandlerMapping 再补一次头浏览器看到重复的 Access-Control-Allow-Origin 直接拒绝这是配完还报 blocked 的隐藏原因。3. 方式一addCorsMappings 全局配置 springboot cors 与 allowedOriginPatterns3.1 最小全局配置与链式参数表Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:5173, https://admin.example.com) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .exposedHeaders(X-Token, Content-Disposition) .allowCredentials(true) .maxAge(3600); } }代码逻辑说明实现 WebMvcConfigurer 的 Configuration 会在启动时被回调addCorsMappings 把 CorsRegistration 注册到 RequestMappingHandlerMapping 的全局配置里所有匹配路径的接口共用一份规则。addMapping 是路径匹配/api/**只覆盖业务接口/**最省事但后面挂 Security 时范围越宽越难管。链式参数对应行为默认值说明allowedOriginsAccess-Control-Allow-Origin无白名单 Origin与 allowCredentials(true) 并存时禁止*allowedOriginPatternsAccess-Control-Allow-Origin无通配模式匹配后回显请求方 OriginallowedMethodsAccess-Control-Allow-MethodsGET/HEAD/POST漏配 PUT/DELETE 时预检直接失败allowedHeadersAccess-Control-Allow-Headers默认不限制显式配置后才按白名单校验exposedHeadersAccess-Control-Expose-Headers空不配的话前端 getResponseHeader 拿不到值allowCredentialsAccess-Control-Allow-Credentialsfalsetrue 表示允许携带 cookie 与 AuthorizationmaxAgeAccess-Control-Max-Age1800 秒浏览器缓存预检结果的时间allowedMethods 默认只有 GET/HEAD/POST 是新手最容易踩的点接口用 PUT 更新前端请求是发出去了但预检先失败Network 里始终只有一条 OPTIONS控制台却不解释为什么。3.2 allowedOrigins 与 allowedOriginPatternscredentialstrue 时的坑前端 axios 一旦withCredentials: true或后端接口要读 cookieAccess-Control-Allow-Origin 就不能是*必须是具体 Origin。老写法.allowedOrigins(*) .allowCredentials(true)在 Spring Framework 5.3 之前能启动但浏览器按规范拒绝5.3 引入 allowedOriginPatterns 后推荐替代方案到 Spring Boot 3 这一代直接这样写会在启动阶段抛 IllegalArgumentException报错信息就是这个场景的经典文案cors 配置错误(反射 origin credentialstrue)。这也是项目从 Boot 2.3 升到 Boot 3 后突然启动失败的高频原因跟 springboot 版本太高、行为收紧直接相关。正确做法是窄化来源或者用模式匹配registry.addMapping(/**) .allowedOriginPatterns(http://localhost:*, https://*.example.com) .allowedMethods(*) .allowCredentials(true);参数说明allowedOriginPatterns 不是把*原样塞进响应头而是后端拿请求 Origin 与模式比对匹配后把实际 Origin 回显到 Access-Control-Allow-Origin。localhost:*能覆盖前端经常变的随机端口*.example.com覆盖多级子域。安全审计严格的话还是建议显式 allowedOrigins 白名单模式匹配是便利和安全的折中。3.3 全局方案的另一个形态注册 CorsFilter Bean部分场景不适合走 MVC 层接口路径不归 RequestMapping 管、想在 Filter 链最前面处理、或者要跟 Spring Security 共用同一个配置源。常见做法是直接注册 CorsFilterBean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.setAllowedOriginPatterns(List.of(http://localhost:*)); config.setAllowedMethods(List.of(*)); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }逻辑说明CorsFilter 构造器依赖一个 CorsConfigurationSourceUrlBasedCorsConfigurationSource 负责按路径返回配置registerCorsConfiguration 可以写多行做按模块的差异化放行。它与 addCorsMappings 的区别在于执行层级一个在 Servlet Filter一个在 MVC HandlerMapping两者同时存在就会产生重复头。我的建议是纯 Spring MVC 项目用 addCorsMappings涉及 Spring Security 时把配置抽成 CorsConfigurationSource 的 Bean 给两边共用避免维护两套。4. 方式二用 CrossOrigin 注解细粒度放行 springboot 接口4.1 类级与方法级的 CrossOrigin 写法RestController RequestMapping(/api/open) CrossOrigin(origins http://localhost:5173, maxAge 3600) public class OpenApiController { GetMapping(/health) public String health() { return ok; } GetMapping(/orders) CrossOrigin( origins {https://admin.example.com, https://ops.example.com}, allowedHeaders {authorization, content-type}, exposedHeaders X-Total-Count, allowCredentials true ) public ListOrder list() { return orderService.list(); } }代码逻辑说明类级注解对 Controller 下所有方法生效方法级注解与类级注解做合并同名属性以方法为准方法没写的属性沿用类级值。所以上面的 health 接口只放行 localhost:5173orders 接口额外放开两个线上来源且允许前端读取 X-Total-Count 响应头。CrossOrigin 的完整属性表属性类型默认值映射目标origins别名 valueString[]{}Access-Control-Allow-OriginallowedHeadersString[]{}Access-Control-Allow-HeadersmethodsRequestMethod[]{}为空时取 Controller 映射方法本身exposedHeadersString[]{}Access-Control-Expose-HeadersallowCredentialsString注意是字符串true/false不是布尔值maxAgelong-1预检缓存秒数-1 表示不产生该头新手最容易写错的是 allowCredentials 传了布尔 trueCrossOrigin 的属性类型是 String传 boolean 会直接编译报错。4.2 细粒度场景第三方来源、自定义请求头与暴露头CrossOrigin 适合系统里只有几个接口对外开放的局面比如健康检查、支付回调、开放平台 API。第三方来源往往不是一个域origins 数组直接列多个。前端要读 X-Total-Count 做分页后端必须 exposedHeaders 声明否则 getResponseHeader 返回 null前端请求头带了自定义 headerallowedHeaders 得包含它否则预检阶段就被否决。注解方式的优点是配置跟着接口走一个接口一套规则代码评审时看 Controller 就清楚谁对谁开放了跨域不需要再全局翻配置类。4.3 注解方案的边界这些场景 CrossOrigin 不生效注解只在请求到达 Spring MVC 的 HandlerMapping 之后才有意义。请求在 DispatcherServlet 之前被 Spring Security、网关或前置 Filter 拦截时注解配置根本没机会作用判断方法是看 Network 里失败响应有没有 Access-Control-Allow-Origin 头有说明 MVC 层处理了没有就往上找拦截层。路径没匹配到 Controller 时同理404 响应不会携带注解生成的跨域头前端报错前先确认 URL 前缀没拼错。另一个容易混的是权限语义CrossOrigin 只决定浏览器是否放行响应不代表接口匿名可访问。认证逻辑照常执行加了注解不等于免登录。实际项目里加了 CrossOrigin 还是 401大多是这个问题和后端 cors 配置本身没关系。5. springboot cors 配完还报 blocked 的 4 个排查点与 curl 验证5.1 排查点一Spring Security 把 OPTIONS 预检挡在门外Configuration public class SecurityConfig { Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors(Customizer.withDefaults()) .authorizeHttpRequests(auth - auth .requestMatchers(HttpMethod.OPTIONS, /**).permitAll() .anyRequest().authenticated()); return http.build(); } }代码说明http.cors()会从容器里找 CorsConfigurationSource Bean没有就用默认实现OPTIONS 先放行否则预检请求会被认证逻辑拦截返回 401响应里自然没有跨域头。Spring Boot 2 的写法是antMatchers(HttpMethod.OPTIONS, /**).permitAll()原理相同。同时确认你在 Security 里用了同一个 CorsConfigurationSource别让 Security 读一套、MVC 用另一套。5.2 排查点二404、自定义拦截器与重复头preflight OPTIONS 请求如果路径不匹配任何 handlerHandlerMapping 直接返回 404响应头里不会有 Access-Control-Allow-Origin浏览器判定预检失败——这跟配置写没写对无关。前端 baseURL 多拼一段、后端 context-path 不一致都会触发。自定义拦截器是第二个坑很多 springboot 拦截器实现里校验登录态对没有 token 的 OPTIONS 直接 return false预检要求 2xx 响应401/403 照样被浏览器拦截。常见做法是在 preHandle 第一行放行 OPTIONS 请求再把业务校验放在后面。第三个常见失败是重复的 Access-Control-Allow-Origin 头。注解和 addCorsMappings 同时生效会叠加nginx、网关层手动 add_header 之后后端 Filter 又补一次也会出现两行同名头浏览器的报错文案是multiple values。用 curl 看响应头两行一样的值就是证据。5.3 用 curl 和 Vary 头验证 cors 配置是否真正生效# 验证真实请求的响应头 curl -s -D - -o /dev/null http://localhost:8080/api/v1/order/1 \ -H Origin: http://localhost:5173 # 验证预检请求前端最常见的失败环节 curl -s -D - -o /dev/null -X OPTIONS http://localhost:8080/api/v1/order/1 \ -H Origin: http://localhost:5173 \ -H Access-Control-Request-Method: GET \ -H Access-Control-Request-Headers: authorization命令说明-D -把响应头打印到标准输出-o /dev/null丢弃 body。看完输出只判断三件事Access-Control-Allow-Origin 的值是否和请求 Origin 完全一致含端口Vary头里是否带 Origin这是缓存区分来源的关键标志credentials 场景下 Allow-Origin 不允许是*。三条都过配置本身没问题剩下的怀疑对象就是浏览器缓存和上层代理任意一条不过直接用返回的状态码和缺失头去定位是哪一层没放行。浏览器里改完配置还报旧错时开发期把 maxAge 临时设成 0让每次请求都重新预检联调通过后再提到 600 秒收尾——用这招能过滤掉一半我明明改对了的自我怀疑。本文还有配套的精品资源点击获取
返回列表