Spring Boot 2.7.10+Spring Security登出重定向的CORS错误问题
我有两个应用:
- 后端服务:Spring Boot应用,运行地址
http://localhost:8080/server - 前端客户端:JavaScript应用,运行地址
http://localhost:9000/app
客户端可通过自定义登录表单携带凭证正常登录,也能使用fetch() API向后端8080端口请求JSON数据。但点击登出按钮时,向后端发送类似 http://localhost:8080/server/logout?originalClientURL=http://localhost:9000/app 的请求后,后端处理重定向到客户端时出现CORS错误:
Access to XMLHttpRequest at 'http://localhost:900/app/'
(redirected from 'http://localhost:8080/server/logout?originalClientURL=http://localhost:9000/app/')
from origin 'http://localhost:9000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
我的Spring Security配置代码如下:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors(cors -> cors.configurationSource(request -> { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("*")); configuration.setAllowedHeaders(Arrays.asList("*")); return configuration; })) .csrf() .disable(); http .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) // 避免Spring默认的POST方式登出表单 .addLogoutHandler((request, response, authentication) -> { // 登出前在服务器执行一些操作 // 之后重定向回客户端地址 String originalClientURL = request.getParameterMap().get("originalClientURL")[0]; response.sendRedirect(originalClientURL); }); } }
问题核心是:用fetch()发送登出AJAX请求时,浏览器会自动跟随重定向,但重定向后的前端地址(9000端口)不会返回CORS响应头,导致跨域拦截。下面是两种可行的解决方式:
方案1:后端返回跳转地址,前端自行处理(推荐)
不要让后端直接重定向,改为返回包含跳转地址的JSON响应,由前端主动跳转,避免AJAX跨域重定向的问题:
后端修改登出处理器
.addLogoutHandler((request, response, authentication) -> { // 执行登出前的业务操作 String originalClientURL = request.getParameter("originalClientURL"); // 设置CORS响应头,确保前端能接收这个响应 response.setHeader("Access-Control-Allow-Origin", "http://localhost:9000"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setContentType("application/json"); // 返回跳转地址 response.getWriter().write("{\"redirectUrl\":\"" + originalClientURL + "\"}"); })
前端修改fetch请求逻辑
fetch('http://localhost:8080/server/logout?originalClientURL=http://localhost:9000/app', { method: 'GET', credentials: 'include' // 携带会话凭证,确保登出生效 }) .then(res => res.json()) .then(data => { window.location.href = data.redirectUrl; // 前端主动跳转 }) .catch(err => console.error(err));
方案2:强制后端重定向响应携带CORS头(不推荐)
如果一定要用后端重定向,需要在sendRedirect前给响应添加CORS头,确保浏览器能接受这个重定向响应:
.addLogoutHandler((request, response, authentication) -> { // 执行登出前的业务操作 String originalClientURL = request.getParameter("originalClientURL"); // 添加CORS头到重定向响应 response.setHeader("Access-Control-Allow-Origin", "http://localhost:9000"); response.setHeader("Access-Control-Allow-Credentials", "true"); // 暴露Location头,让前端可以获取重定向地址(可选) response.setHeader("Access-Control-Expose-Headers", "Location"); response.sendRedirect(originalClientURL); })
注意:这种方式要求前端应用本身能返回CORS头,但前端一般是静态页面或SPA,很少配置这类头,所以方案1更稳妥。
补充:优化全局CORS配置
不要用*允许所有来源,明确指定前端地址更安全,同时开启凭证支持:
http.cors(cors -> cors.configurationSource(request -> { CorsConfiguration configuration = new CorsConfiguration(); // 明确指定允许的前端域名,支持多个可添加多个地址 configuration.setAllowedOrigins(Arrays.asList("http://localhost:9000")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); // 允许携带会话凭证(如cookie、token) configuration.setAllowCredentials(true); return configuration; }))
内容的提问来源于stack exchange,提问作者Bằng Rikimaru

