SpringWebsocket 整合认证实战

SpringWebsocket 提供了支持 Websocket 协议的模块,允许客户端和服务端进行实时操作。

它支持 Stomp 支持和 SockJS。

SpringWebSocket 配置

依赖

js 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

首先我们要取消掉SpringSecurity 拦截websocket

java 复制代码
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    // 禁用CSRF和Session管理,可以根据项目需要启用或禁用
    httpSecurity
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(AbstractHttpConfigurer::disable)
            .headers().frameOptions().disable()
            .and()
            // 配置请求授权规则--- /websocket/** 允许所有用户访问
            .authorizeHttpRequests(author ->
                    author.antMatchers("/admin/code", "/admin/login", "/websocket/**").permitAll()

配置 WebSocket

@EnableWebSocketMessageBroker 首先启用 Websocket 消息代理,允许处理Websocket 消息。

我们再实现 WebSocketMessageBrokerConfigurer,用于配置消息代理的各个方面。

消息代理的目标、应用程序的前缀、跨域、认证等。

java 复制代码
@Configuration
@EnableWebSocketMessageBroker
// @Order(Ordered.HIGHEST_PRECEDENCE + 999)
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

}

配置消息代理

通过重写 configureMessageBroker 进行配置消息代理。

通过 MessageBrokerRegistry 的 enableSimpleBroker 方法配置 /topic 的代理,表示客户端可以订阅 以/topic为前缀的目标,用于接收信息。

通过 MessageBrokerRegistry的setApplicationDestinationPrefixes方法设置应用程序的前缀,客户端发送信息将以/app 为前缀,才能被服务端进行处理。

通过 setUserDestinationPrefix 方法设置点对点消息地址

java 复制代码
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    //设置客户端接收点对点消息地址的前缀,默认为 /user
    config.setUserDestinationPrefix("/user");
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
}

注册 STOMP

通过重写 registerStompEndpoints 方法,进行注册端点,一种简单的消息协议。

通过 StompEndpointRegistry 的 addEndpoint方法,进行注册名为 websocket 端点。

通过 setAllowedOriginPatterns 设置允许跨域。* 表示允许所有。

通过 withSockJS 设置启动 SockJS 的支持,提供了对WebSocket API的抽象,使得在WebSocket不可用时,客户端仍然能够使用其他传输方式与服务器进行通信。

java 复制代码
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/websocket")
            .setAllowedOriginPatterns("*")
            .withSockJS();
}

配置认证

通过重写 configureClientInboundChannel 方法,表示入站拦截器,其中我们自定义一个拦截器,从中取出 Token 获取用户,并设置身份信息。

其中我们重写拦截器的 preSend 方法,表示在发送请求之前拦截。

有两个参数,Message<?> 参数表示 WebSocket 消息,MessageChannel 参数表示消息发送的通道。

StompHeaderAccessor 类 用于访问STOMP消息的头部信息,比如命令(CONNECT、SEND、SUBSCRIBE等)和头部信息。

我们就从这个类中获取我们存入的 Token 令牌。

我们检查是否是连接信息,连接信息包含 accessor.getCommand() 的数据,如果为null就不是连接,直接放行即可。

如果是,进行认证, 通过 getFirstNativeHeader 方法获取我们指定的 Token 名,一般为 Authorization

最后重点是, accessor.setUser(userPrincipal) 一定要把 User 对象存入啊。后期直接获取,最后返回 Message 就好啦。

不要忘记把创建的拦截器加入哦, registration.interceptors(interceptor);

java 复制代码
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    ChannelInterceptor interceptor = new ChannelInterceptor() {
        @Override
        public Message<?> preSend(Message<?> message, MessageChannel channel) {
            // 获取消息头访问器,用于处理STOMP消息的头信息
            StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

            // 检查访问器和命令是否为空
            if (accessor == null || accessor.getCommand() == null) {
                return null; // 返回空表示继续处理下一个拦截器
            }

            // 处理CONNECT命令,用于用户认证
            if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                // 从消息头中获取Authorization头(包含用户的身份认证token)
                String token = accessor.getFirstNativeHeader("Authorization");
                
                // 如果token为空,表示没有身份认证信息,返回空继续处理下一个拦截器
                if (token == null) {
                    return null;
                }
                
                // 剥离token前缀,获取原始token
                token = token.replace(Constants.TOKEN_PREFIX, "");
                
                // 通过token服务获取LoginUser对象,用于创建用户Principal
                LoginUser loginUser = tokenService.getLoginUser(token);
                UserPrincipal userPrincipal = new UserPrincipal(loginUser);
                
                // 将用户Principal设置到访问器中,以便后续处理使用
                accessor.setUser(userPrincipal);
            }

            // 返回处理后的消息,继续后续处理
            return message;
        }
    };

    // 将拦截器注册到WebSocket消息处理的Channel
    registration.interceptors(interceptor);
}

后期在控制器获取用户对象的方法。

@MessageMapping 和 requestMapping 一样,放在类上,表示一级路经,放在方法上,表示二级路径。

用于表示接收时的路径。

@SendTo 是返回到当前Message路径, @SendToUser 是返回到当前Message路径,并是当前用户。

simpMessagingTemplate 是SpringWebsocket 提供的,用于主动推送信息给客户端。

simpMessagingTemplate.convertAndSend 表示向所有在线用户发送信息,参数1是订阅地址,参数2是数据。

java 复制代码
@RestController
@MessageMapping("/cover/channel")
public class ChannelSocketController extends BaseController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/deptChannel")
    @SendTo("/topic/deptChannel")
    public AjaxResult getDeptByIdSocket(ManholeCover manholeCover, UserPrincipal userPrincipal) {
        simpMessagingTemplate.convertAndSend("/topic/deptChannel", success);
        return success;
    }
}

前端 stomp 连接

创建 Stomp 客户端,设置ws地址,并链接,订阅user消息

安装

js 复制代码
@stomp/stompjs
pnpm i sockjs-client
js 复制代码
import SockJS from 'sockjs-client/dist/sockjs.min.js'
const sockJs = new SockJS('http://localhost:4646/websocket');
const client = Stomp.over(sockJs)
client.connect({"Authorization": 'Bearer ' + getToken()}, () => {

    // 加载地图信息,是后端的MessageMapping路径
    client.send('/app/cover/channel/deptChannel', {"Authorization": 'Bearer ' + getToken()}, JSON.stringify({}));

    // 订阅消息,是后端的 @SendTo 路径
    client.subscribe('/topic/deptChannel', (message) => {
        console.log('Received222222:', message.body);
    })
})
相关推荐
ab123768几秒前
MQTT笔记
java·笔记·mqtt·学习
恋猫de小郭1 分钟前
Flitz ,一个把 Flutter 玩成热插拔 Preview 的有趣项目,不错的思路
android·前端·flutter
闭包不眠3 分钟前
Word转HTML后体积不对,裁掉的截图原图还在
前端·图像处理·人工智能·计算机视觉·html·word
二宝哥5 分钟前
【问题】代理转发到后端使用request.getRequestURL()地址没有端口
java
言乐69 分钟前
PythonLOLM自动生成复盘报告模型
开发语言·前端·数据库·人工智能·python
霸道流氓气质9 分钟前
HashiCorp Vault 与云 KMS 入门实战:动态密钥、SecretLeaseContainer 与 Java 集成完整指南
java·开发语言
wuminyu12 分钟前
stackChunkOop内部结构与GC屏障的配合工作原理剖析
java·linux·c语言·jvm·c++
xuxigifxfh17 分钟前
牛客:HJ21 简单密码
java·开发语言·算法
小π军18 分钟前
C 和 C++ 是否都支持函数的重载?
java·c语言·c++
tryCbest21 分钟前
第二篇:CSS3 全部知识点全解 + 项目样式实现
前端·css·css3