SpringBoot在集成STOMP协议时,有些时候需要控制session的连接权限、订阅权限等。例如禁止某些用户连接,控制用户只能订阅自己的主题等。而且在这种场景权限的控制比较简单,一般情况下,可以使用拦截器,进行权限管理。
架构图
- 客户端去业务系统进行认证授权,将授权信息存放redis。并且返回客户端token。
- 客户端收到token,携带token连接推送服务器。推送服务器判断token,有效则认证通过。
每个业务服务器的代码都基本一致,就不做展示,直接示例下第二步的代码。
服务端代码
配置拦截器
WebSocketConfig
package com.test.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.WebSocketMessageBrokerStats;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{
// 启用一个简单的基于内存的消息代理
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//通过/topic 开头的主题可以进行订阅
config.enableSimpleBroker("/topic");
//send命令时需要带上/app前缀
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//连接前缀
registry.addEndpoint("/gs-guide-websocket")
.setAllowedOrigins("*") // 跨域处理
.withSockJS(); //支持socketJs
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
//设置拦截器,这儿是个数组, 可添加多个
registration.interceptors(new MyChannelInterceptor());
}
}
MyChannelInterceptor类,示例中暂时写了连接事件和订阅事件,可在这两个事件中自行处理对应的逻辑,权限验证等等。
@Slf4j
public class MyChannelInterceptor implements ChannelInterceptor
{
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getCommand ();
if(command == StompCommand.CONNECT){
String userId = accessor.getFirstNativeHeader ("userId");
// String token = accessor.getFirstNativeHeader ("token");
log.info ("-------有用户连接-------");
//todo 连接时可以做权限验证
}else if (command == StompCommand.SUBSCRIBE){
log.info ("-------有用户订阅{}-------",accessor.getDestination ());
}
return message;
}
}
StompCommand
是一个枚举类,里面有STOMP的连接事件,有连接事件、订阅事件、取消订阅、发送事件等等。
public enum StompCommand {
// client
STOMP(SimpMessageType.CONNECT),
CONNECT(SimpMessageType.CONNECT),
DISCONNECT(SimpMessageType.DISCONNECT),
SUBSCRIBE(SimpMessageType.SUBSCRIBE, true, true, false),
UNSUBSCRIBE(SimpMessageType.UNSUBSCRIBE, false, true, false),
SEND(SimpMessageType.MESSAGE, true, false, true),
ACK(SimpMessageType.OTHER),
NACK(SimpMessageType.OTHER),
BEGIN(SimpMessageType.OTHER),
COMMIT(SimpMessageType.OTHER),
ABORT(SimpMessageType.OTHER),
// server
CONNECTED(SimpMessageType.OTHER),
RECEIPT(SimpMessageType.OTHER),
MESSAGE(SimpMessageType.MESSAGE, true, true, true),
ERROR(SimpMessageType.OTHER, false, false, true);
}
这些事件说明也很容易理解,是一些STOMP协议的基本事件。
H5代码
一般情况下,在进行webSocket连接时,都已经获取到了用户信息,即使没有用户信息,在当前的js页面也会有一个唯一的客户端Id。在连接的时候将客户端的信息添加到请求头。示例代码中,提交了userId,实际情况可自行修改。
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
var userId = 1;
stompClient.connect({'userId':userId}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/user'+userId, function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
},{'userId':userId});
});
}