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

Spring Boot 2.0.0集成Websocket+JWT认证失败,求技术指导

Spring Boot 2.0.0 + SockJS Websocket 结合JWT认证解决方案

我帮你梳理下完整的实现步骤,亲测适配2.0.0.RELEASE版本,照着来应该能解决你的问题:

一、先调整Security配置,放行WebSocket握手路径

首先得让WebSocket的握手请求(HTTP请求)能绕过常规拦截走到我们的自定义认证逻辑,补充你的SecurityConfig:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final TokenAuthenticationProvider tokenAuthenticationProvider;
    private final JwtTokenUtil jwtTokenUtil; // 假设你已有这个JWT工具类

    public SecurityConfig(TokenAuthenticationProvider tokenAuthenticationProvider, JwtTokenUtil jwtTokenUtil) {
        this.tokenAuthenticationProvider = tokenAuthenticationProvider;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                // 放行WebSocket握手端点,这里假设你的端点是/ws/**
                .antMatchers("/ws/**").permitAll()
                // 其他REST接口保持原有JWT认证逻辑
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(authenticationManager(), jwtTokenUtil), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(tokenAuthenticationProvider);
    }
}

二、实现握手拦截器,提取JWT并完成认证

SockJS的握手是HTTP请求,所以我们可以通过HandshakeInterceptor拦截请求,从Header里拿Token做认证,把用户信息存入WebSocket会话:

@Component
public class JwtHandshakeInterceptor implements HandshakeInterceptor {

    private final JwtTokenUtil jwtTokenUtil;
    private final AuthenticationManager authenticationManager;

    public JwtHandshakeInterceptor(JwtTokenUtil jwtTokenUtil, AuthenticationManager authenticationManager) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.authenticationManager = authenticationManager;
    }

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        // 从请求头获取Token,格式为Bearer xxx
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        String tokenHeader = servletRequest.getServletRequest().getHeader("Authorization");
        
        if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) {
            String token = tokenHeader.substring(7);
            String username = jwtTokenUtil.getUsernameFromToken(token);
            
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 构造认证Token,调用你的认证逻辑验证有效性
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
                Authentication authentication = authenticationManager.authenticate(authToken);
                
                if (authentication.isAuthenticated()) {
                    // 把用户信息存入WebSocket会话属性,后续Handler可以用
                    attributes.put("authenticatedUser", authentication.getPrincipal());
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    return true;
                }
            }
        }
        
        // 认证失败,返回401拒绝握手
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return false;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        // 清理线程上下文,避免污染后续请求
        SecurityContextHolder.clearContext();
    }
}

三、配置WebSocket和SockJS,注册拦截器

接下来配置WebSocket端点,启用SockJS并绑定我们的握手拦截器:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final JwtHandshakeInterceptor jwtHandshakeInterceptor;

    public WebSocketConfig(JwtHandshakeInterceptor jwtHandshakeInterceptor) {
        this.jwtHandshakeInterceptor = jwtHandshakeInterceptor;
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 配置消息代理,客户端订阅/topic前缀的消息
        config.enableSimpleBroker("/topic");
        // 客户端发送消息的前缀,比如/app/chat
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .addInterceptors(jwtHandshakeInterceptor)
                .setAllowedOrigins("http://your-frontend-domain.com") // 生产环境指定具体域名,别用*
                .withSockJS();
    }
}

四、在WebSocket Handler中获取认证用户

现在在你的消息处理类里,可以从会话中拿到已认证的用户信息:

@Controller
public class ChatController {

    @MessageMapping("/chat/send")
    @SendTo("/topic/chat/messages")
    public ChatMessage broadcastMessage(ChatMessage input, SimpMessageHeaderAccessor headerAccessor) {
        // 从会话属性中取出认证用户
        UserDetails user = (UserDetails) headerAccessor.getSessionAttributes().get("authenticatedUser");
        if (user == null) {
            throw new RuntimeException("未认证用户无法发送消息");
        }
        // 组装带用户名的消息返回
        return new ChatMessage(user.getUsername(), input.getContent(), LocalDateTime.now());
    }
}

五、前端连接时携带JWT Token

前端用SockJS连接时,要在请求头里带上Authorization,示例JS代码:

import SockJS from 'sockjs-client';
import Stomp from 'stompjs';

const socket = new SockJS('/ws');
const stompClient = Stomp.over(socket);

// 连接时传入Token
stompClient.connect(
  { Authorization: `Bearer ${localStorage.getItem('jwtToken')}` },
  (frame) => {
    console.log('WebSocket连接成功:', frame);
    // 订阅消息
    stompClient.subscribe('/topic/chat/messages', (message) => {
      const chatMsg = JSON.parse(message.body);
      // 处理收到的消息
      console.log(chatMsg);
    });
  },
  (error) => {
    console.error('WebSocket连接失败:', error);
  }
);

关键注意点

  • 确保你的JWT Token在握手时未过期,否则认证会直接失败
  • 生产环境务必不要用setAllowedOrigins("*"),要指定前端的具体域名
  • 如果你的Token是放在URL参数里(不推荐,不安全),可以在beforeHandshake里通过request.getQueryParams().getFirst("token")获取
  • 确认spring-boot-starter-websocket依赖已经正确引入到你的项目中

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 02:28:14