如何配置Spring Security使其接受JSON格式的CSRF令牌?
Spring Security JSON请求下登出403(CSRF验证失败)解决方案
问题根源
Spring Security默认仅从application/x-www-form-urlencoded表单或multipart/form-data表单中解析CSRF令牌,不会主动解析JSON请求体中的CSRF参数。因此用JSON格式发送登出请求时,CSRF验证会失败,返回403状态码。
解决步骤
1. 自定义CSRF令牌解析处理器
创建处理器类,让Spring Security支持从请求头或JSON请求体中提取CSRF令牌:
import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.util.Map; public class JsonCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler { private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler(); @Override public void handle(HttpServletRequest request, HttpServletResponse response, CsrfToken csrfToken) { this.delegate.handle(request, response, csrfToken); } @Override public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { // 优先从请求头提取令牌(推荐方式) String headerValue = request.getHeader(csrfToken.getHeaderName()); if (StringUtils.hasText(headerValue)) { return headerValue; } // 尝试从JSON请求体提取令牌 try { ObjectMapper objectMapper = new ObjectMapper(); Map<String, String> requestBody = objectMapper.readValue(request.getInputStream(), Map.class); return requestBody.get(csrfToken.getParameterName()); } catch (Exception e) { // 最后回退到默认表单参数解析逻辑 return this.delegate.resolveCsrfTokenValue(request, csrfToken); } } }
2. 配置Spring Security使用自定义处理器
修改Security配置类,替换默认的CSRF请求处理器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .csrfTokenRequestHandler(jsonCsrfTokenRequestHandler()) ) .logout(logout -> logout .logoutUrl("/logout") .permitAll() ); // 其他授权、认证规则配置 return http.build(); } @Bean public CsrfTokenRequestHandler jsonCsrfTokenRequestHandler() { return new JsonCsrfTokenRequestHandler(); } }
3. 前端请求调整(二选一)
方式一:通过请求头传递CSRF令牌(更推荐)
从页面meta标签或cookie中获取令牌,放在Spring Security默认识别的请求头中:
const csrfToken = document.querySelector('meta[name="_csrf"]').content; const csrfHeader = document.querySelector('meta[name="_csrf_header"]').content; fetch('/logout', { method: 'POST', headers: { [csrfHeader]: csrfToken, 'Content-Type': 'application/json' } });
方式二:通过JSON请求体传递CSRF令牌
确保请求体中的令牌键名与Spring Security默认参数名(_csrf)一致:
const csrfToken = document.querySelector('meta[name="_csrf"]').content; fetch('/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ _csrf: csrfToken }) });
补充说明
multipart/form-data能成功登出,是因为Spring Security默认会解析multipart请求的参数,CSRF令牌可以被正常提取;而JSON请求体默认不在解析范围内,导致令牌无法识别,触发403。
内容的提问来源于stack exchange,提问作者joe
相关产品推荐
相关产品推荐

