在Spring中使用STOMP和WebSocket向特定用户发送消息时,检查身份验证

8
我正在使用内置的消息代理和WebSocket上的STOMP,在Spring 4中开发实时通知系统。
我希望能够按照用户名向特定用户发送消息。为了实现这个目标,我使用了org.springframework.messaging.simp.SimpMessagingTemplate类的convertAndSendToUser方法,如下所示:
private final MessagingTemplate messagingTemplate;

@Autowired
public LRTStatusListener(SimpMessagingTemplate messagingTemplate) {
    this.messagingTemplate = messagingTemplate;
}


@Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
    messagingTemplate
        .convertAndSendToUser(principal.getName(), "/horray", "Horray, " + principal.getName() + "!");
}

作为配置:

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/notifications").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic", "/queue", "/user");
    }

}

通过JavaScript来实现客户端,我应该根据用户名订阅一个频道(参考另一个非常相似的问题:在Spring Websocket上向特定用户发送消息)。

stompClient.subscribe('/user/' + username + '/horray, ...) 

这最后一个点听起来很奇怪...
假设我在我的Web应用程序上登录为w.white,通过订阅:
stompClient.subscribe('/user/w.white/horray, ...)

我将能够查看发送给w.white的消息,这太棒了...但是订阅:

stompClient.subscribe('/user/j.pinkman/horray, ...)

我将能够查看发送给j.pinkman的消息,尽管我当前已登录为w.white

有什么方法可以解决这个问题吗?


更新

下面是WebSocket连接日志:

Opening Web Socket... 
Web Socket Opened... 
>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

<<< CONNECTED
user-name:w.white
heart-beat:0,0
version:1.1

connected to server undefined
Connected: CONNECTED
version:1.1
heart-beat:0,0
user-name:w.white

>>> SUBSCRIBE
id:sub-0
destination:/topic/lrt

>>> SUBSCRIBE
id:sub-1
destination:/user/lrt
2个回答

25

我找到了解决方案。

首先,重要的是要知道/user频道已经由Spring STOMP管理,而且顺便说一句,不需要注册。

所以:

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/topic", "/queue");
}

然后,我将目标频道设置为/queue/horray

@Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
    messagingTemplate
        .convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!");
}

最终,在客户端上:

stompClient.subscribe('/user/queue/horray', '...');

现在,它运行良好!根据安全上下文获取的Principal,消息仅发送给指定的接收者。


2
恭喜你的解决方案。你知道是否有使用@SendToUser注释的类似版本吗? - hawaii
1
我的配置几乎和你的一样(我使用队列/回复而不是队列/欢呼),但我的控制台输出为空。你在帖子中跳过了一些重要的点还是这就是全部?你能否发布完整的配置? - user2104560
3
你好,@vdenotaris。我注意到你安排了一个定时发布消息的方法:@Scheduled(fixedDelay=5000) public void sendMessages(Principal principal) messagingTemplate .convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!");Spring的定时方法不支持参数。那么我该如何在这个定时方法中获取Principal呢?提前感谢! - 0bj3ct
2
任何使用@Scheduled注解的方法都不能接受任何参数。例如:@Scheduled(fixedDelay=5000) public void sendMessages(Principal principal){},这段代码是无法工作的。 - Ashish Bhosle
1
@vdenotaris 我没有使用Spring安全 :-(,我该如何在这里获取用户 messagingTemplate.convertAndSendToUser(?, "/queue/horray", "Horray, ?"); - Shantaram Tupe
显示剩余3条评论

7

由于我的应用程序上的用户没有经过身份验证,因此我只使用了会话ID来区分不同的主题

在服务器端:

template.convertAndSend("/topic/warnings/" + sessionId, ...)

客户端非常简单明了。

stompClient.subscribe('/topic/warnings/${pageContext.session.id}', ...

也许这不是最干净的方法,但它可以运行,并且如果没有身份验证,我无法使用/user通道。

在我的情况下,如果两个用户登录到同一个浏览器中,他们的sessionId将是相同的,因此一个人可以看到另一个人的订阅,这应该基于用户ID或用户名来完成... - Shantaram Tupe
@ShantaramTupe 在我看来,两个用户同时登录同一个浏览器并不是一个有效的情况。在这种情况下,您将不得不在内部切换会话。然而,实际上最终只有一个用户拥有多个帐户,例如 Gmail。 - Romeo Sierra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接