ARTICLE DETAIL

资讯详情

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

Spring Security跨域配置指南:从预检请求到CORS实战

Spring Security跨域配置指南:从预检请求到CORS实战 1. 先从根上理解浏览器为什么要有跨域这一说1.1 同源策略到底在保护什么做前后端分离开发的人几乎都踩过跨域这个坎。我第一次在联调现场被前端同事喊过去看到浏览器控制台里报“Access to XMLHttpRequest ... has been blocked by CORS policy”第一反应是后端哪里写错了。拿Postman一测接口好好的数据正常返回。后来才搞明白真正让人头疼的不是接口本身而是浏览器在替你做安全检查。这个安全检查就是同源策略。同源的定义很简单两个URL的协议、域名、端口必须完全一致。比如前端页面跑在 http://localhost:8080后端接口在 http://localhost:9090端口不同就是跨源。浏览器规定跨源请求拿到的响应默认不能被JavaScript读取目的很朴素——防止恶意网站通过你已经保存好的Cookie在后台偷偷调用你在其他网站上已经登录的接口这就是经典的CSRF攻击场景。如果没有这条限制一个野鸡网页只要发一个Ajax请求到你注册过的某个站点就能拿你的身份做坏事那才叫灾难。所以跨域不是后端的bug也不是前端的锅它是浏览器的一种安全边界。你在前端控制台看到的红色报错其实是浏览器在说“这个接口的响应没有告诉我它允许你的网站访问所以我不能把数据交给页面。”跨域问题要解决必须在服务端返回特定的响应头告诉浏览器“这个Origin我允许”。这个机制就是CORS。1.2 简单请求和预检请求的分界线在哪里CORS全称是Cross-Origin Resource Sharing跨域资源共享是目前主流的跨域解法。它的核心不是“允许跨域”这个开关概念而是一组HTTP响应头。服务端在响应里声明允许哪些来源访问浏览器核对通过后才会把响应结果交给页面JavaScript。没有这组响应头就算接口数据本身完全正常浏览器也会毫不客气地把响应丢进垃圾桶。但CORS并不仅仅是加个响应头这么简单。浏览器把跨域请求分成两类。简单请求是指用GET、HEAD或POST并且Content-Type严格限制在application/x-www-form-urlencoded、multipart/form-data、text/plain三种传统类型中而且不带自定义请求头的请求。这类请求浏览器会直接发出去等响应回来以后检查响应头没声明CORS就报错。问题来了现代前后端接口要么用POST加application/json要么带着Authorization之类的自定义头这些都不满足简单请求的条件会被归类为复杂请求。复杂请求发送之前浏览器会先发一个HTTP方法为OPTIONS的预检请求Preflight询问服务端我准备用POST携带JSON结构还带一个叫Authorization的头你赞成还是反对服务端赞成的话就在预检响应里返回允许的Method、Header、Origin。浏览器核对通过才会发出真正的业务请求。预检请求本身不携带业务数据但它经常因为带着Origin头而被安全框架拦下来这就是我后面要反复展开的那个坑。提示预检请求本质上是浏览器替真实请求“探路”。如果服务端不响应预检或者返回4xx/5xx浏览器会直接判定跨域失败真实请求根本不会发出。所以排查跨域时第一步永远是看Network面板里OPTIONS请求的状态码而不是盯着业务接口的返回。1.3 Spring Security和CORS的第一层关系很多从Spring MVC转过来的人以为跨域是Spring MVC的事情在Controller上加CrossOrigin或者在WebMvcConfigurer里配addCorsMappings就完事了。这个经验在纯Spring MVC项目里确实成立但一旦引入Spring Security情况就变了。Spring Security在请求处理流程中的位置非常靠前。一个HTTP请求进入应用后首先要穿过Servlet容器的过滤器链Spring Security的FilterChainProxy就是其中的一环它内部还有一串安全过滤器负责认证、授权、CSRF、跨域等横切逻辑。这些过滤器跑完之后请求才到达Spring MVC的DispatcherServlet进而进入Controller层。这样问题就清晰了如果你只在Spring MVC层配置CORS跨域响应头是在MVC的处理过程中才被写出来的而安全过滤器早就先跑完了。预检OPTIONS请求到达Spring Security时没有任何跨域逻辑在处理它Spring Security默认又会对请求做认证授权检查一个没带凭证的OPTIONS请求大概率直接被拒绝返回403或401。浏览器拿到的还是一个没有Access-Control-Allow-Origin头的响应于是一口咬定跨域失败。这就是Spring Security项目里“配了跨域还是报跨域”的最常见原因。2. 只配了Spring MVC跨域却被Spring Security拦截问题出在哪2.1 一次被拦截的OPTIONS请求的完整经历顺着请求链路走一遍问题就很直观了。假设前端在 http://localhost:8080后端在 http://localhost:9090前端用Axios发一个POST请求带JSON和JWT。浏览器发现是跨域且属于复杂请求立刻先发OPTIONS预检。预检请求进入后端经过Tomcat的Filter链到FilterChainProxySpring Security开始处理接着请求到了Spring MVC的DispatcherServlet最终命中某个Controller的OPTIONS映射。但绝大多数Controller根本没有OPTIONS映射。通过WebMvcConfigurer配置CORS时Spring Framework的CorsInterceptor是在MVC的HandlerMapping执行过程中介入的它会处理跨域请求。对于预检请求来说处理器是AbstractHandlerMapping内部内置的PreFlightHandler它会自己把OPTIONS请求处理掉返回必要的CORS头。这些处理都发生在MVC框架层时间上已经很靠后了。真正的问题出在链路中段。Spring Security的过滤器在检查请求时如果发现请求没带认证信息而访问的路径又需要认证就会直接向浏览器返回403或401响应头里根本没有CORS相关的字段。浏览器看到没有Access-Control-Allow-Origin直接判定预检失败前端控制台那串红色报错就是这么来的。你后端接口本身一点问题都没有要是用Postman直接发业务请求绝对能通但浏览器就是不让页面拿到结果。2.2 过滤器链的先后顺序Security在MVC之前还是之后Spring Security在Servlet过滤器链中的位置决定了它和MVC的CORS处理的先后关系。按Servlet规范所有Filter都是注册在Web应用容器层面的FilterChainProxy是其中一个普通Filter。DispatcherServlet作为Servlet是由容器直接触发的所以从整体时序上看Security的过滤器组一定跑在DispatcherServlet之前。更精确地说Spring Security内部还维护了它自己的过滤器链顺序。常见的过滤器包括SecurityContextHolderFilter、HeaderWriterFilter、CorsFilter、CsrfFilter、LogoutFilter、UsernamePasswordAuthenticationFilter、ExceptionTranslationFilter、FilterSecurityInterceptor等。其中CorsFilter的位置比较靠前在认证逻辑之前这是Spring官方有意为之——跨域这种“门卫级”的处理必须最先完成否则后续的认证授权会对预检请求造成干扰。如果你在SecurityFilterChain中开启了cors()Spring Security会在安全过滤器链里插入一个CorsFilter。预检请求进来CorsFilter直接读取CorsConfigurationSource里的配置生成CORS响应头并返回200后面的认证过滤器根本不会执行到。这是最干净的处理路径。反过来如果没开cors()安全过滤器链里没有跨域处理器OPTIONS请求就会一路往后走撞上认证授权逻辑被打回。无论你的SecurityFilterChain里是简单的formLogin、httpBasic还是接入了OAuth2资源服务器、自定义JWT过滤器这个时序关系都不会变。2.3 跨域配置的三种入口别混用也别漏配跨域配置的入口总结下来有三个。第一个是Spring MVC层的WebMvcConfigurer#addCorsMappings面向Controller层的全局配置第二个是CrossOrigin注解面向单个Controller或方法级别的局部配置第三个是Spring Security的HttpSecurity#cors()面向安全过滤器链的配置。三者不是互相替代的关系它们各自负责请求生命周期中不同的阶段。最理想的状态是只有一个入口在Security层配置CorsConfigurationSource开启cors()预检请求在安全链路最前段就能被处理。Controller上要么干脆不出现CrossOrigin要么只做细颗粒度补充但没必要。Spring Boot项目里如果你在WebMvcConfigurer中配了CORS又在Security中调用了cors()Boot会自动使用一个HandlerMappingIntrospector作为CorsConfigurationSource的来源把MVC层配置“导入”到安全层所以这种情况也能正常工作。但如果你换成一个裸的Spring项目没有Boot的自动装配兜底只配MVC层CORS而忽略Security层预检照样被拦。很多人的困惑“我明明按MVC的方式配了跨域怎么还会报错”根源就是这种入口混杂和Boot兜底机制带来的错觉。3. Spring Security中正确的跨域配置实操3.1 推荐方案CorsConfigurationSource Bean cors()到了实操环节我先给出最推荐的一套配置。整个思路是把跨域规则定义成一个CorsConfigurationSource的Bean然后在SecurityFilterChain里调用cors()并通过configurationSource指向它。这样一来无论预检请求还是真实业务请求都会被安全链中的CorsFilter统一处理完全不依赖MVC层的配置。Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(cors - cors.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth - auth .requestMatchers(HttpMethod.OPTIONS, /**).permitAll() .anyRequest().authenticated() ) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config new CorsConfiguration(); config.setAllowedOriginPatterns(List.of(http://localhost:*, https://*.example.com)); config.setAllowedMethods(List.of(GET, POST, PUT, DELETE, OPTIONS)); config.setAllowedHeaders(List.of(*)); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return source; } }这段代码有三个细节值得展开。第一用的是setAllowedOriginPatterns而不是setAllowedOrigins原因我放在3.4专门讲。第二requestMatchers(HttpMethod.OPTIONS, /**).permitAll()是双保险即使CorsFilter因为某些原因没有拦住这个预检请求OPTIONS也能被放行到MVC层去处理。第三setMaxAge(3600L)让浏览器在1小时内复用预检结果避免每个请求都先来一遍OPTIONS对接口性能有明显帮助。我这里是JWT无状态模式如果你用SessionsessionManagement那一段可以去掉。3.2 兼容方案WebMvcConfigurer与Boot的自动装配如果你在旧项目里已经在WebMvcConfigurer中配了一份addCorsMappings想不动它靠Boot的自动装配也能让Spring Security的跨域正常运转前提是SecurityFilterChain里至少调用了cors()。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(http://localhost:*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }然后在SecurityConfig里写http.cors(Customizer.withDefaults())。因为ApplicationContext中有一个由Boot自动提供的CorsConfigurationSource默认候选cors()会自动采用它。此时安全过滤链中的CorsFilter和MVC层的CorsInterceptor同时存在配置源是同一份不会出现不一致。坏处是这件事太隐蔽了新同学接手代码时看到cors()只有一行完全想不起来去看WebMvcConfigurer排障的时候绕很大一个弯。所以我的建议是新代码一律用CorsConfigurationSource Bean老代码如果已经在MVC层配好可以临时靠这个机制但重构时还是应该收拢到Security层统一管理。3.3 注解方案CrossOrigin到底能干啥CrossOrigin是Spring MVC三种入口中粒度最细的那个。它可以放在Controller类上对整个类的方法生效也可以只放在某个方法上。使用起来很简单但局限性也很明显。第一它依赖MVC层的处理在Spring Security的过滤器链中不会产生任何影响。这意味着如果Security层没有cors()且没有显式放行OPTIONS预检请求仍会被安全链拦截CrossOrigin等于白配。第二它把跨域规则写死在代码里如果将来要动态放行某个来源得改代码重新发布。第三如果多处配置存在冲突比如类级别和方法级别的CrossOrigin规则不一致容易产生令人困惑的报错。所以我的定位是CrossOrigin适合那种实验性的小项目或者某个模块独享一套跨域规则的特殊场景。正规的前后端分离项目我不建议用注解来管跨域。跨域是横切关注点放在安全层的统一配置里管理才是正道。你可以把整个项目里的CrossOrigin当成一个待去除的技术债逐步用CorsConfigurationSource收编。3.4 一个关键约束allowCredentials和allowedOrigins不能乱组合这是一块很多人栽过跟头的地方。CorsConfiguration里有几个互相关联的属性allowedOrigins允许的来源、allowedOriginPatterns允许的来源模式、allowCredentials是否允许携带凭证比如Cookie或Authorization头。默认情况下allowCredentials是false浏览器跨域请求不会自动带Cookie。如果你希望前端调用接口时携带登录后种下的Cookie需要把allowCredentials设为true。但这样设置之后allowedOrigins就绝对不能是“”通配符。因为“”意味着允许任意来源再配合allowCredentials(true)等于允许任何网站带着你的Cookie来调用接口这是严重的安全漏洞。CORS规范明确禁止这种组合Spring会在启动时直接抛异常错误信息大概是When allowCredentials is true, allowedOrigins cannot contain the special value *。解决方案就是我前面用的allowedOriginPatterns。它可以写通配符比如“http://localhost:”允许本地任意端口“https://.example.com”允许example.com的任意子域名同时和allowCredentials一起使用完全合法。这个API的语义比allowedOrigins更灵活如果你有多个测试环境、子域名经常变化用Pattern可以避免每次新增环境都改代码重新发布。简单记一句话只要开了allowCredentials就不要碰allowedOrigins(*)一律用allowedOriginPatterns。4. 前后端分离项目中的跨域完整落地4.1 Spring Boot 3 Spring Security 6完整配置代码光有SecurityConfig还不够真实项目里跨域和JWT、拦截规则、异常处理是纠缠在一起的。我直接给一个在Spring Boot 3 Spring Security 6下能跑起来的完整配置骨架把跨域放到整个安全体系里来看。Configuration EnableWebSecurity EnableMethodSecurity public class SecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, CorsConfigurationSource corsConfigurationSource) throws Exception { http .cors(cors - cors.configurationSource(corsConfigurationSource)) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/**, /api/public/**).permitAll() .requestMatchers(HttpMethod.OPTIONS, /**).permitAll() .anyRequest().authenticated() ) .exceptionHandling(ex - ex.authenticationEntryPoint(customAuthenticationEntryPoint())) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config new CorsConfiguration(); config.setAllowedOriginPatterns(List.of(http://localhost:8080, https://*.example.com)); config.setAllowedMethods(List.of(GET, POST, PUT, DELETE, PATCH, OPTIONS)); config.setAllowedHeaders(List.of(*)); config.setExposedHeaders(List.of(X-Total-Count, Authorization)); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return source; } }注意我把OPTIONS的permitAll放在了所有接口规则的最前面因为预检请求本身不带业务数据永远不应该被安全拦截。另外setExposedHeaders也很重要如果前端需要读取响应头里的自定义字段比如X-Total-Count用于分页或者Authorization用于刷新Token必须在ExposedHeaders里显式声明否则浏览器会把这些头隐藏起来前端读取到的是null。这个细节我第一次上线时就漏了前端同事怎么都拿不到分页总数最后一行一行对响应头才发现是ExposedHeaders的问题。4.2 前端Axios/Fetch的配合点后端把CORS配置好后前端也不是完全无事可做。以Axios为例如果你需要在跨域请求中携带Cookie就必须显式开启withCredentials。import axios from axios; axios.defaults.baseURL http://localhost:9090/api; axios.defaults.withCredentials true; // 跨域携带Cookie axios.defaults.timeout 10000; axios.interceptors.request.use(config { const token localStorage.getItem(access_token); if (token) { config.headers.Authorization Bearer ${token}; } return config; });开启withCredentials之后后端必须保持allowCredentials(true)同时不用allowedOrigins(*)否则就是前面说的那个冲突。用Fetch也一样fetch(url, { credentials: include })。还有一个和跨域纠缠很深的前端场景是本地代理。比如前端项目用Vite可以通过devServer配置proxy把 /api 代理到后端这样浏览器看到的是同源请求自然没有跨域问题。开发阶段这个方案特别好用因为后端没配CORS时也不会报错。但注意这只能用于开发环境生产环境还是要靠后端真正的CORS。如果你用uniapp开发H5可以在manifest.json里配置h5.devServer.proxy实现本地代理或者直接依赖后端CORS二选一即可。不要同时叠加代理又开着跨域请求路径和响应头都会变得混乱。4.3 网关统一跨域与下游不重配的取舍微服务架构里请求往往先过网关再转发到业务服务。这时候跨域配置的最佳实践是“在网关层统一处理”。Spring Cloud Gateway的配置如下spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true cors-configurations: [/**]: allowedOriginPatterns: * allowedMethods: * allowedHeaders: * allowCredentials: true maxAge: 3600allowedOriginPatterns: *在网关层可以这样用因为它同时配合了allowCredentials。输出给浏览器的响应头里网关会用实际请求的Origin动态填充Access-Control-Allow-Origin而不是写死一个星号这就是Pattern和星号origin之间的区别。关键经验是网关配了跨域之后下游业务服务就不要再配CORS了。否则浏览器可能收到两个Access-Control-Allow-Origin响应头前后不一致前端照样跨域失败这个坑我踩过不止一次。要守住的边界是只有直接面向浏览器的入口网关需要CORS下游服务只被网关内网调用它们完全不需要也不应该做跨域配置。如果某个服务同时被网关和其他外部客户端直接调用那才需要单独评估。4.4 本地开发环境和工作生产环境的配置差异本地开发和生产环境的跨域配置常见差异有三个。第一来源范围不同。本地一般是localhost和127.0.0.1的一些端口生产往往是固定的几个域名所以allowedOriginPatterns的范围可以精准化。第二HTTPS问题。如果生产环境是HTTPS前端页面来源可能是https://app.example.com你把配置里的来源写成http来源对不上照样失败。第三Cookie的SameSite属性。跨域携带Cookie时如果Cookie设置的是SameSiteLax或Strict且没有设置Secure浏览器压根不会在跨域请求中带上它。这一点排查起来特别迷惑——后端明明配了allowCredentials(true)前端也开了withCredentialsCookie却就是不发送。我的建议是配置Cookie时如果跨域且走HTTPS使用SameSiteNone加Secure如果是本地http调试SameSite可以设置成Lax或者干脆本地不用Cookie改用Token。把环境差异前置考虑清楚能少很多“后端说配好了、前端说没通”的隔空喊话时间。5. 跨域排障实录从浏览器报错到根因定位5.1 高频问题成因与速查对照表我把这些年遇到的各种跨域问题整理成一张表方便你遇到问题直接对照排查。现象大概率根因处理方向OPTIONS预检请求返回403Security链中的cors()未配置或CorsConfigurationSource缺失补齐CorsConfigurationSource并开启cors()预检返回200业务请求仍被拦自定义请求头不在allowedHeaders允许范围内使用allowedHeaders(*)或明确列出自定义头响应头没有Access-Control-Allow-OriginCORS处理逻辑根本没执行到检查cors()调用、Filter顺序、Bean可见性响应头出现两个Access-Control-Allow-Origin网关层和服务层同时配置了跨域收拢配置到网关层或去掉一层携带凭证时浏览器直接拒绝allowCredentials与allowedOrigins(*)冲突改用allowedOriginPatterns前端读不到自定义响应头自定义头未在exposedHeaders中声明增加exposedHeaders配置Cookie在跨域请求中不发送SameSite属性限制或Secure未设置配置SameSiteNone并加SecureHTTPS下接口302到登录页后跨域报错未认证请求被重定向到登录页302响应缺CORS头改用Token认证或放行登录接口这张表解决的是“方向对不上”的问题。真正定位的时候还要依赖下面这些调试动作。5.2 用浏览器Network面板准确定位是预检失败还是响应头缺失排查跨域第一步永远是打开浏览器开发者工具的Network面板刷新页面重新发起请求然后重点看两个请求OPTIONS预检请求和紧随其后的真实请求。观察OPTIONS的状态码——如果403说明安全层就拦截了优先查Security配置如果200再点开响应头检查是否包含Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers。三项必须全部符合浏览器的预期才算合格。如果Network面板里只看到一个请求真实请求压根没发出来那就是预检阶段就失败了。这时候用curl模拟预检请求非常管用curl -i -X OPTIONS http://localhost:9090/api/auth/login \ -H Origin: http://localhost:8080 \ -H Access-Control-Request-Method: POST \ -H Access-Control-Request-Headers: content-type,authorization看返回的响应头和状态码。如果服务端返回了正确的Access-Control-Allow-*头说明服务端配置没问题问题可能出在浏览器缓存或代理上。这个操作能非常有效地把“后端问题”和“前端问题”切开避免两边互相怀疑、在群里来回拉扯。5.3 容易被忽略的三处细节缓存、重复头、自定义Filter第一处细节是浏览器对跨域结果的缓存。如果CORS配置最近改过旧页面可能还拿着老的预检缓存导致你看到的现象和最新配置不一致。改完配置后用无痕窗口重新测试是最快排除变量干扰的办法。同时不要忘了后端已经通过maxAge让浏览器缓存预检结果缓存期内的任何代码变更都不会被浏览器感知所以调试阶段先把maxAge设小一点比如60秒改配置就能很快生效。第二处细节是响应头可能重复。网关和业务服务同时配置CORS是最常见的重复头根源但还有一种情况同一个项目里既用Security的cors()又显式注入了一个Servlet层面的CorsFilter。两个过滤器都会往响应里写Access-Control-Allow-Origin结果浏览器拿到两个重复头直接报错。检查方法很简单在Network面板里点开响应头看同一个字段出现了几次。第三处细节是自定义Filter的入侵。如果你在SecurityFilterChain里自己注册了Filter又在Filter里直接写了一部分响应头或者把某个OPTIONS请求提前return返回就可能导致CorsFilter根本没有机会把跨域头写完整。这种问题光看配置是看不出来的一定要顺着过滤器链的执行顺序走一遍。有条件的话把Spring Security和CORS相关的日志级别调成DEBUG可以在控制台看到过滤器链的真实执行顺序问题出在哪一环一目了然。5.4 我反复踩坑后的几条硬核建议第一跨域配置要统一收口到Security层不要在Controller注解、MVC配置、Security配置三个地方各配一遍。来源收口之后排障时只需要看一个文件。第二Cookie相关的问题优先级高先确认allowCredentials、allowedOriginPatterns、SameSite、withCredentials这几个点的状态再去看头的配置。第三把预检请求的排查动作固定下来无论前端报什么跨域第一反应都应该是先在Network面板确认OPTIONS的返回码而不是直接改代码。第四跨域排查的日志意识很重要开启Security的DEBUG日志一次请求的过滤器链顺序清晰可见很多玄学问题会瞬间变得有迹可循。我也要特别说一句不要图省事去关浏览器的跨域安全策略或者用那种“浏览器启动加参数”的本地绕过方法。那是把安全问题整个规避掉换个环境马上现原形还会连累一起调试的同事。老老实实把服务端的CORS配置做好才是唯一值得走的路。我个人更偏爱无状态Token方案它把Session和Cookie从跨域场景里彻底摘出去很多坑自然就没了。当然项目总有历史包袱不是每个团队都能说改就改但新项目立项时优先考虑Token模式会让跨域处理简单不少。另外像JSONP这种老古董方案基本可以退役了它只支持GET请求还得后端配合返回可执行的JavaScript代码安全风险也不小远不如CORS规范、可控、前后端配合起来也简单。理解了CORS背后的浏览器安全模型再回头看看Spring Security里的那些配置你会发现其实没有玄学说到底就是一句话谁先碰到请求谁负责放行。
返回列表