You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:20:26