1. WebSocket 握手认证的痛点与解决方案选型
在实时通信场景中,WebSocket 协议因其全双工通信特性被广泛应用,但握手阶段的身份认证一直是开发者面临的棘手问题。传统方案如 Cookie 或 Session ID 在跨域场景下存在兼容性问题,而 JWT 等无状态方案又难以应对连接状态的实时管理。这正是 Sa-Token 框架的用武之地——它提供了轻量级且与协议无关的认证机制。
我在实际项目中遇到过这样的典型场景:某金融实时行情系统需要同时满足高并发连接和严格的身份鉴权。最初采用 Basic Auth 方案,结果发现每次重连都要传输敏感凭证;改用 JWT 后,又面临 Token 过期无法主动踢人的困境。最终通过 Sa-Token 的 Token 鉴权模型完美解决了以下核心问题:
- 无状态握手:仅需在连接时验证一次 Token,后续通信无需重复认证
- 会话绑定:通过 Sa-Token 的会话管理可精准控制每个 WebSocket 连接的生命周期
- 权限隔离:基于路由的权限验证可细化到每个消息端点(endpoint)
2. 基于 Sa-Token 的认证体系设计
2.1 核心组件依赖配置
在 Spring Boot 项目中引入必要依赖(示例为 Maven):
<dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.34.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>关键配置项说明:
sa-token: token-name: satoken # HTTP头中携带的Token名称 timeout: 86400 # Token有效期(秒) activity-timeout: -1 # 无操作永不过期 is-concurrent: true # 允许并发登录 is-share: true # 在WebSocket中共享会话2.2 认证流程时序设计
- 前端连接初始化:
const socket = new WebSocket( `wss://api.example.com/ws?token=${getToken()}` );- 后端握手拦截器(关键代码节选):
public class WsAuthInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) { // 从URL参数提取Token String token = request.getURI().getQuery() .split("token=")[1].split("&")[0]; // Sa-Token核心验证 if(!StpUtil.isLogin()) { StpUtil.checkLogin(token); } // 绑定会话到WebSocket attributes.put("loginId", StpUtil.getLoginId()); return true; } }- 会话绑定原理: Sa-Token 通过 ThreadLocal 维护会话上下文,在 WebSocket 握手阶段将 Token 对应的登录ID 存入连接属性。后续消息处理时可通过以下方式获取当前用户:
String loginId = (String) session.getAttributes().get("loginId");3. 完整实现方案与避坑指南
3.1 服务端实现全流程
3.1.1 配置类编写
@Configuration @EnableWebSocket public class WsConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/ws") .addInterceptors(new WsAuthInterceptor()) .setAllowedOrigins("*"); } @Bean public WebSocketHandler myHandler() { return new MyWsHandler(); } }3.1.2 消息处理器示例
public class MyWsHandler extends TextWebSocketHandler { @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 获取绑定用户 Long userId = (Long) session.getAttributes().get("loginId"); // 业务处理(示例:权限验证) if(!StpUtil.hasPermission("ws:trade")) { session.sendMessage(new TextMessage("无操作权限")); return; } // 消息处理逻辑... } }3.2 高频问题解决方案
问题1:Token 过期导致连接中断
- 现象:连接建立后,Token 过期导致后续操作失败
- 解决方案:
// 在消息处理器中添加Token刷新逻辑 if(StpUtil.getTokenTimeout() < 3600) { StpUtil.renewTimeout(86400); }问题2:跨域携带 Cookie 失效
- 根本原因:浏览器安全策略限制
- 最佳实践:
// 前端连接时显式携带Token const socket = new WebSocket( `wss://api.example.com/ws`, [localStorage.getItem('satoken')] );问题3:心跳检测与会话保持
- 推荐方案:实现Ping/Pong机制
// 服务端配置 registry.addHandler(myHandler(), "/ws") .setAllowedOrigins("*") .withSockJS() .setHeartbeatTime(30000); // 30秒心跳4. 高级应用场景拓展
4.1 分布式环境适配
在微服务架构下,需要额外配置 Sa-Token 的 Redis 插件:
<dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-dao-redis</artifactId> <version>1.34.0</version> </dependency>配置示例:
sa-token: is-share: true is-read-cookie: false token-style: uuid ><dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.86.Final</version> </dependency>- 连接参数调优
@Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); container.setMaxSessionIdleTimeout(600000L); return container; }4.3 安全加固措施
- 防重放攻击:
// 在拦截器中添加时间戳验证 long timestamp = Long.parseLong( request.getURI().getQuery() .split("timestamp=")[1].split("&")[0] ); if(System.currentTimeMillis() - timestamp > 5000) { throw new RuntimeException("请求已过期"); }- 流量控制:
// 基于Sa-Token的限流组件 @SaCheckLimit(value=100, time=60) public void handleMessage(WebSocketSession session, String message) { // 每分钟最多处理100条消息 }在实际部署中发现,当连接数超过5000时,原生的Tomcat WebSocket实现会出现明显的性能下降。通过切换到Netty+Sa-Token的组合方案后,单机连接数可稳定支持2W+,且认证耗时保持在5ms以内。关键配置项中特别要注意sa-token.is-share=true这个参数,它确保了HTTP和WebSocket协议间的会话共享。