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
相关产品推荐
相关产品推荐

