【Spring Boot 2.7 整合 WebSocket 完整实战】鉴权拦截+在线用户管理+定向消息推送
前言
在实时通信场景(消息推送、在线聊天、数据实时更新)中,WebSocket 是首选方案!相比传统HTTP轮询,WebSocket 支持全双工通信 ,一次连接永久互通,性能大幅提升。
本文基于 Spring Boot 2.7.x 版本,从零整合 Spring 官方的 WebSocket 组件,实现自定义Token鉴权拦截、多设备在线用户管理、定向/广播消息推送等核心功能,代码可直接复制复用!
目录
- 核心Maven依赖引入
- 自定义WebSocket握手拦截器(Token鉴权)
- 在线用户会话管理工具类(核心)
- 自定义WebSocket消息处理器
- WebSocket核心配置类
- 前端测试与功能调用
- 常见问题&注意事项
一、核心Maven依赖引入
Spring Boot 提供了官方的 WebSocket 启动器,无需额外兼容,直接引入即可:
xml
<!-- Spring Boot WebSocket 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
二、自定义WebSocket握手拦截器(Token鉴权)
WebSocket 建立连接前,我们可以通过握手拦截器实现自定义鉴权(如Token校验、登录状态判断),校验通过才允许建立连接。
实现 HandshakeInterceptor 接口,重写握手前后方法:
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import java.util.Map;
/**
* WebSocket 自定义鉴权拦截器
* 作用:连接建立前校验用户登录状态/Token
*/
@Slf4j
public class DemoAuthInterceptor implements HandshakeInterceptor {
/**
* 握手前执行(核心:鉴权逻辑)
* @return true=允许连接,false=拒绝连接
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
try {
// 👇 自定义鉴权逻辑:获取登录用户(替换为你项目的用户获取工具类)
MobileLoginUser mobileLoginUser = MobileLoginHelper.getUser();
// 将用户信息存入session属性,供后续处理器使用
attributes.put(Constant.MobileLoginUser, mobileLoginUser);
log.info("WebSocket鉴权成功,用户:{}", mobileLoginUser.getParentCode());
return true;
} catch (Exception e) {
log.error("WebSocket鉴权失败:{}", e.getMessage());
return false;
}
}
/**
* 握手后执行(无需处理可空实现)
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {}
}
关键说明
beforeHandshake:连接建立前的核心鉴权方法,返回false直接拒绝连接;attributes:用于传递数据,鉴权通过后可将用户信息存入,后续处理器直接获取;- 替换提示:
MobileLoginUser/MobileLoginHelper/Constant为项目自定义类,根据你的业务修改。
三、在线用户会话管理工具类(核心)
这是 WebSocket 实战的核心工具类:
- 管理在线用户会话(支持多设备同时在线);
- 实现用户与WebSocket会话的绑定/解绑;
- 提供定向发送消息 、全局广播消息能力。
使用 ConcurrentHashMap + CopyOnWriteArraySet 保证线程安全:
java
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 移动端WebSocket 在线用户管理工具类
* 核心:记录在线用户、绑定/解绑会话、消息推送
*/
public class MobileWebSocketUserHolder {
/**
* 在线用户存储
* key: 用户唯一标识(如parentCode)
* value: 该用户的所有WebSocket会话(支持多设备)
*/
private static final Map<String, Set<WebSocketSession>> ONLINE_USER_MAP = new ConcurrentHashMap<>();
/**
* 绑定用户与WebSocket会话(连接成功时调用)
*/
public static void bindSession(MobileLoginUser user, WebSocketSession session) {
if (user == null || user.getParentCode() == null) {
return;
}
String parentCode = user.getParentCode();
// 不存在则创建集合,存在则直接添加会话
ONLINE_USER_MAP.computeIfAbsent(parentCode, k -> new CopyOnWriteArraySet<>()).add(session);
}
/**
* 解绑用户与WebSocket会话(连接关闭时调用)
*/
public static void unbindSession(MobileLoginUser user, WebSocketSession session) {
if (user == null || user.getParentCode() == null) {
return;
}
String parentCode = user.getParentCode();
Set<WebSocketSession> sessions = ONLINE_USER_MAP.get(parentCode);
if (sessions != null) {
sessions.remove(session);
// 会话为空则移除该用户
if (sessions.isEmpty()) {
ONLINE_USER_MAP.remove(parentCode);
}
}
}
/**
* 获取所有在线用户ID
*/
public static Set<String> getOnlineParentCodes() {
return new HashSet<>(ONLINE_USER_MAP.keySet());
}
/**
* 判断用户是否在线
*/
public static boolean isOnline(String parentCode) {
return parentCode != null && ONLINE_USER_MAP.containsKey(parentCode);
}
/**
* 给【指定用户】发送文本消息
*/
public static void sendMessageToUser(String parentCode, String message) {
if (!isOnline(parentCode)) {
log.warn("用户{}不在线,消息发送失败", parentCode);
return;
}
Set<WebSocketSession> sessions = ONLINE_USER_MAP.get(parentCode);
for (WebSocketSession session : sessions) {
try {
if (session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
} catch (Exception e) {
log.error("给用户{}发送消息失败", parentCode, e);
}
}
}
/**
* 给【所有在线用户】广播消息
*/
public static void sendMessageToAll(String message) {
ONLINE_USER_MAP.keySet().forEach(parentCode -> sendMessageToUser(parentCode, message));
}
}
四、自定义WebSocket消息处理器
继承 AbstractWebSocketHandler 重写生命周期方法,处理连接建立、消息接收、连接关闭、异常捕获:
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
/**
* WebSocket 自定义消息处理器
* 处理连接、消息、关闭、异常全生命周期
*/
@Slf4j
public class DemoWebSocketHandler extends AbstractWebSocketHandler {
/**
* 连接建立成功:绑定用户 → 记录在线状态
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// 从拦截器传递的属性中获取登录用户
MobileLoginUser loginUser = (MobileLoginUser) session.getAttributes().get(Constant.MobileLoginUser);
if (loginUser != null) {
// 绑定用户与会话
MobileWebSocketUserHolder.bindSession(loginUser, session);
log.info("[WebSocket] 登录成功, 当前在线数:{}",
MobileWebSocketUserHolder.getOnlineParentCodes().size());
}
log.info("[WebSocket] 连接建立 | sessionId:{}", session.getId());
}
/**
* 接收客户端发送的文本消息
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
String payload = message.getPayload();
log.info("[WebSocket] 收到客户端消息:{}", payload);
// 可选:回复客户端消息
// session.sendMessage(new TextMessage("服务端已收到:" + payload));
}
/**
* 连接关闭:清理会话 → 更新在线列表
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
MobileLoginUser loginUser = (MobileLoginUser) session.getAttributes().get(Constant.MobileLoginUser);
if (loginUser != null) {
// 解绑用户会话
MobileWebSocketUserHolder.unbindSession(loginUser, session);
log.info("[WebSocket] 家长退出 | parentCode:{} | 当前在线数:{}",
loginUser.getParentCode(),
MobileWebSocketUserHolder.getOnlineParentCodes().size());
}
log.info("[WebSocket] 连接关闭 | sessionId:{} | 原因:{}", session.getId(), status.getReason());
}
/**
* 传输异常处理
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
log.error("[WebSocket] 传输异常 | sessionId:{}", session.getId(), exception);
}
}
五、WebSocket核心配置类
最后通过配置类注册处理器、拦截器、跨域规则,开启WebSocket功能:
java
import cn.hutool.core.util.StrUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
/**
* WebSocket 核心配置类
* 1. 开启WebSocket
* 2. 注册处理器+拦截器+跨域
* 3. 支持开关控制
*/
@Configuration
@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true") // 配置开关
@EnableWebSocket // 开启WebSocket支持
public class WebSocketConfig {
/**
* 注册WebSocket处理器、拦截器、跨域配置
*/
@Bean
public WebSocketConfigurer webSocketConfigurer(DemoAuthInterceptor demoAuthInterceptor,
DemoWebSocketHandler demoWebSocketHandler) {
return registry -> {
// 注册自定义处理器:访问路径 /ws/vaccine
registry.addHandler(demoWebSocketHandler, "/ws/vaccine")
.addInterceptors(demoAuthInterceptor) // 添加自定义鉴权拦截器
.setAllowedOrigins("*"); // 允许跨域(生产环境可指定域名)
// 👇 可新增多个处理器,配置不同路径和拦截器
// registry.addHandler(otherHandler, "/ws/other").addInterceptors(otherInterceptor);
};
}
/**
* 注入自定义拦截器
*/
@Bean
public DemoAuthInterceptor demoAuthInterceptor() {
return new DemoAuthInterceptor();
}
/**
* 注入自定义处理器
*/
@Bean
public DemoWebSocketHandler demoWebSocketHandler() {
return new DemoWebSocketHandler();
}
}
配置说明
@ConditionalOnProperty:通过配置文件websocket.enabled=true/false控制WebSocket是否启用;setAllowedOrigins("*"):允许所有跨域,生产环境建议替换为具体域名(如http://localhost:8080);- 支持多处理器配置:不同业务模块可注册不同的WebSocket路径,互不干扰。
六、前端测试与功能调用
1. 前端连接WebSocket
使用原生JS连接,连接地址:
ws://localhost:8080/ws/vaccine
前端测试代码:
javascript
// 创建WebSocket连接
const socket = new WebSocket('ws://localhost:8080/ws/vaccine');
// 连接成功
socket.onopen = function () {
console.log('WebSocket连接成功');
socket.send('Hello WebSocket');
};
// 接收服务端消息
socket.onmessage = function (evt) {
console.log('收到服务端消息:', evt.data);
};
// 连接关闭
socket.onclose = function () {
console.log('WebSocket连接关闭');
};
2. 后端调用消息推送
在业务代码中直接调用工具类即可:
java
// 给指定用户发消息
MobileWebSocketUserHolder.sendMessageToUser("1001", "您有新的通知");
// 给所有用户广播消息
MobileWebSocketUserHolder.sendMessageToAll("系统公告:服务升级中");
七、常见问题&注意事项
1. 线程安全
必须使用 ConcurrentHashMap + CopyOnWriteArraySet,避免高并发下的会话数据异常。
2. 跨域问题
前端连接报错跨域时,检查配置类中setAllowedOrigins是否配置正确。
3. 鉴权失效
确保拦截器中正确获取用户信息 ,并将用户存入attributes,否则处理器无法获取用户导致绑定失败。
4. 会话泄漏
连接关闭时必须调用unbindSession,否则会导致无效会话堆积,内存溢出。
5. Spring Boot版本兼容
本文基于 2.7.x,3.x版本仅包路径不同,核心代码完全通用。
总结
本文实现了 Spring Boot 2.7 整合 WebSocket 的企业级实战方案 ,包含:
✅ 自定义Token鉴权拦截
✅ 多设备在线用户管理
✅ 定向/广播消息推送
✅ 可配置、可扩展的架构设计
代码无冗余、可直接复用,适用于消息推送、在线客服、实时数据展示等绝大多数实时业务场景!
原创不易,觉得有用的话点赞+收藏+关注,后续更新更多Spring Boot实战教程~