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

Spring Boot与STOMP下WebSocket的CSRF验证问题咨询

基于HTTP的STOMP WebSocket CSRF验证实现方案

你遇到的问题是Spring Security默认的WebSocket CSRF验证流程依赖HTTP握手阶段传递Token,但你的场景无法在握手时携带自定义头,且GET请求默认不触发CSRF校验,导致后续CsrfChannelInterceptor无法验证STOMP头中的Token。以下是几种可行的实现方案:

方案一:强制握手GET请求校验CSRF,通过Cookie传递Token

Spring Security默认不对GET请求做CSRF校验,但可以针对WebSocket握手路径强制开启,同时用Cookie传递Token(Spring Security推荐的方式),让CsrfTokenHandshakeInterceptor自动读取:

1. Spring Security配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                // 配置Cookie存储CSRF Token,允许前端读取
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // 仅对WebSocket握手路径(假设为/ws)强制CSRF校验
                .requireCsrfProtectionMatcher(request -> 
                    "/ws".equals(request.getRequestURI()) && "GET".equals(request.getMethod())
                )
            )
            .authorizeRequests(auth -> auth
                .antMatchers("/ws").authenticated()
                .anyRequest().permitAll()
            );
    }
}

2. WebSocket配置

无需额外修改默认拦截器,CsrfTokenHandshakeInterceptor会自动从Cookie中读取XSRF-TOKEN,并存入WebSocket会话,后续CsrfChannelInterceptor会验证STOMP头中的X-XSRF-TOKEN是否匹配。

前端处理

前端从Cookie中读取XSRF-TOKEN,在STOMP连接时放入头中:

const token = document.cookie.split('; ').find(row => row.startsWith('XSRF-TOKEN=')).split('=')[1];
const stompClient = new StompJs.Client({
    brokerURL: 'ws://localhost:8080/ws',
    connectHeaders: {
        'X-XSRF-TOKEN': token
    }
});

方案二:自定义拦截器,直接验证STOMP头中的Token

如果无法在握手阶段传递Token,可以跳过握手时的Token存储,直接在STOMP消息阶段验证Token与后端存储的一致性:

自定义CsrfChannelInterceptor

public class CustomStompCsrfInterceptor extends CsrfChannelInterceptor {
    private final CsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        // 仅在CONNECT阶段验证Token
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            // 从STOMP头获取Token
            String submittedToken = accessor.getFirstNativeHeader("X-XSRF-TOKEN");
            // 从当前请求或会话获取预期的Token
            HttpServletRequest request = (HttpServletRequest) accessor.getSessionAttributes().get("javax.servlet.http.request");
            CsrfToken expectedToken = tokenRepository.loadToken(request);
            
            if (expectedToken == null || !expectedToken.getToken().equals(submittedToken)) {
                throw new CsrfException("Invalid CSRF Token");
            }
        }
        return super.preSend(message, channel);
    }
}

配置WebSocket拦截器

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // 替换默认拦截器为自定义实现
        registration.interceptors(new CustomStompCsrfInterceptor());
    }

    // 其他WebSocket配置(如代理、前缀等)...
}

方案三:通过查询参数传递Token并让握手拦截器识别

如果必须用查询参数传递,需要确保Spring Security对握手GET请求开启CSRF校验,让CsrfTokenHandshakeInterceptor读取_csrf参数:

Security配置调整

http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
    .requireCsrfProtectionMatcher(request -> 
        "/ws".equals(request.getRequestURI()) && "GET".equals(request.getMethod())
    )
);

前端握手URL

将Token作为_csrf参数拼接到握手URL中:

const token = document.cookie.split('; ').find(row => row.startsWith('XSRF-TOKEN=')).split('=')[1];
const stompClient = new StompJs.Client({
    brokerURL: `ws://localhost:8080/ws?_csrf=${token}`
});

此时CsrfTokenHandshakeInterceptor会读取查询参数中的Token并存入会话,后续STOMP消息的头验证流程正常工作。


内容的提问来源于stack exchange,提问作者Olli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 16:31:03