【Spring Boot 2.7 整合 WebSocket 完整实战】鉴权拦截+在线用户管理+定向消息推送

【Spring Boot 2.7 整合 WebSocket 完整实战】鉴权拦截+在线用户管理+定向消息推送

前言

在实时通信场景(消息推送、在线聊天、数据实时更新)中,WebSocket 是首选方案!相比传统HTTP轮询,WebSocket 支持全双工通信 ,一次连接永久互通,性能大幅提升。

本文基于 Spring Boot 2.7.x 版本,从零整合 Spring 官方的 WebSocket 组件,实现自定义Token鉴权拦截、多设备在线用户管理、定向/广播消息推送等核心功能,代码可直接复制复用!


目录

  1. 核心Maven依赖引入
  2. 自定义WebSocket握手拦截器(Token鉴权)
  3. 在线用户会话管理工具类(核心)
  4. 自定义WebSocket消息处理器
  5. WebSocket核心配置类
  6. 前端测试与功能调用
  7. 常见问题&注意事项

一、核心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) {}
}

关键说明

  1. beforeHandshake:连接建立前的核心鉴权方法,返回false直接拒绝连接;
  2. attributes:用于传递数据,鉴权通过后可将用户信息存入,后续处理器直接获取;
  3. 替换提示: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();
    }
}

配置说明

  1. @ConditionalOnProperty:通过配置文件websocket.enabled=true/false控制WebSocket是否启用;
  2. setAllowedOrigins("*"):允许所有跨域,生产环境建议替换为具体域名(如http://localhost:8080);
  3. 支持多处理器配置:不同业务模块可注册不同的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实战教程~

相关推荐
真实的菜2 小时前
Spring Boot 升级全攻略:从 2.2 到 2.7 再到 3.x
java·spring boot·后端
傲文博一2 小时前
在 Mac 上管理上千台服务器,我把低效操作拆成了 6 个可优化点
后端
字节高级特工2 小时前
C++从入门到熟悉:深入剖析const和constexpr
前端·c++·人工智能·后端·算法
honor_zhang2 小时前
Vue3使用@vueuse/core集成Websocket实战及携带身份信息的3种方式
websocket·网络协议·身份验证
金銀銅鐵2 小时前
[Java] Byte Buddy 和 InvocationHandler 的结合
java·后端
独断万古他化2 小时前
【Java 实战项目】多用户网页版聊天室:项目总览与用户 & 好友管理模块实现
java·spring boot·后端·websocket·mybatis
小杍随笔2 小时前
【Rust 半小时速成(2024 Edition 更新版)】
开发语言·后端·rust
tsyjjOvO2 小时前
SpringBoot 整合 MyBatis
java·spring boot·mybatis
中国胖子风清扬3 小时前
实战:基于 Camunda 8 的复杂审批流程实战指南
java·spring boot·后端·spring·spring cloud·ai·maven