SpringWebsocket 整合认证实战
SpringWebsocket 提供了支持 Websocket 协议的模块,允许客户端和服务端进行实时操作。
它支持 Stomp 支持和 SockJS。
SpringWebSocket 配置
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
首先我们要取消掉SpringSecurity 拦截websocket
@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,用于配置消息代理的各个方面。
消息代理的目标、应用程序的前缀、跨域、认证等。
@Configuration
@EnableWebSocketMessageBroker
// @Order(Ordered.HIGHEST_PRECEDENCE + 999)
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
}
配置消息代理
通过重写 configureMessageBroker 进行配置消息代理。
通过 MessageBrokerRegistry 的 enableSimpleBroker 方法配置 /topic 的代理,表示客户端可以订阅 以/topic为前缀的目标,用于接收信息。
通过 MessageBrokerRegistry的setApplicationDestinationPrefixes方法设置应用程序的前缀,客户端发送信息将以/app 为前缀,才能被服务端进行处理。
通过 setUserDestinationPrefix 方法设置点对点消息地址
@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不可用时,客户端仍然能够使用其他传输方式与服务器进行通信。
@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);
@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是数据。
@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消息
安装
@stomp/stompjs
pnpm i sockjs-client
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 路径,如果前缀加 user表示接收自己的,或者对方指定向自己发。
client.subscribe('/topic/deptChannel', (message) => {
console.log('Received222222:', message.body);
})
})
转载自:https://juejin.cn/post/7345310754470821922