Spring Security 6.2.1+Vue3(axios) POST请求CSRF令牌无效(403)求助
Spring + Vue SPA CSRF防护失效排查方案
问题背景
开发Spring+Vue单页应用时,CSRF防护无法正常启用。已参考相关方案配置,但POST请求始终无法识别CSRF令牌(尝试过_csrf、X-XSRF-TOKEN、X-XSRF、X-CSRF-TOKEN等名称),响应头已包含SET-COOKIES字段携带令牌,但验证始终失败。Axios自动携带和手动添加令牌均无效。
后端Spring Security配置代码
@Configuration @EnableWebSecurity @EnableMethodSecurity(securedEnabled = true) public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf((csrf) -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) ) .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); return http.build(); } } final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler { private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler(); @Override public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) { /* * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of * the CsrfToken when it is rendered in the response body. */ this.delegate.handle(request, response, csrfToken); } @Override public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { /* * If the request contains a request header, use CsrfTokenRequestAttributeHandler * to resolve the CsrfToken. This applies when a single-page application includes * the header value automatically, which was obtained via a cookie containing the * raw CsrfToken. */ if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) { return super.resolveCsrfTokenValue(request, csrfToken); } /* * In all other cases (e.g. if the request contains a request parameter), use * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies * when a server-side rendered form includes the _csrf request parameter as a * hidden input. */ return this.delegate.resolveCsrfTokenValue(request, csrfToken); } } final class CsrfCookieFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(CsrfCookieFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CsrfToken csrfToken = (CsrfToken) request.getAttribute("X-CSRF-TOKEN"); // Render the token value to a cookie by causing the deferred token to be loaded csrfToken.getToken(); filterChain.doFilter(request, response); } }
前端Axios请求代码
export async function validateObservationsId(producer_id: string, dataset_id: string, document_ids: string[]): Boolean[] { const url = `${myBaseUrl}/download/observation/${producer_id}/${dataset_id}/exists`; // 从Cookie获取CSRF令牌 const csrfToken = document.cookie .split('; ') .find(row => row.startsWith('XSRF-TOKEN=')) ?.split('=')[1]; console.log("CSRF Token: ", csrfToken); try { let response = await axios.post( url, document_ids, { headers: { 'X-CSRF-TOKEN': csrfToken } } ); return response.data; } catch (error) { console.error('Error validating observation IDs', error); return []; } }
排查与解决方案
1. 修复CsrfCookieFilter中的令牌属性名错误
当前CsrfCookieFilter中硬编码的请求属性名X-CSRF-TOKEN不正确,Spring Security默认将CsrfToken存储在CsrfToken.class.getName()对应的属性中,修改代码:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); if (csrfToken != null) { csrfToken.getToken(); } filterChain.doFilter(request, response); }
2. 对齐令牌Cookie与请求头名称
CookieCsrfTokenRepository默认生成的Cookie名称是XSRF-TOKEN,对应的请求头应为X-XSRF-TOKEN(而非X-CSRF-TOKEN)。
方案一:修改前端请求头
headers: { 'X-XSRF-TOKEN': csrfToken }
方案二:后端显式配置头名称
csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse() .headerName("X-CSRF-TOKEN"))
3. 确保Axios自动携带令牌的配置正确
Axios默认读取XSRF-TOKENCookie并添加X-XSRF-TOKEN请求头,但需确保:
- 跨域场景下开启
withCredentials - 配置与后端一致的令牌名称
添加Axios全局配置:
axios.defaults.withCredentials = true; axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'; axios.defaults.xsrfCookieName = 'XSRF-TOKEN';
4. 验证请求实际发送的信息
通过浏览器开发者工具(Network标签)确认:
- 请求头中是否包含正确的CSRF令牌字段
- 令牌值与Cookie中的
XSRF-TOKEN是否一致 - 跨域请求是否携带了Cookie
内容的提问来源于stack exchange,提问作者Romb38
相关产品推荐
相关产品推荐

