在 SockJS+Spring Websocket 中,convertAndSendToUser 中的“用户”来自哪里?

Posted

技术标签:

【中文标题】在 SockJS+Spring Websocket 中,convertAndSendToUser 中的“用户”来自哪里?【英文标题】:Where "user" comes from in convertAndSendToUser works in SockJS+Spring Websocket? 【发布时间】:2016-10-17 15:33:36 【问题描述】:

我想了解 convertAndSendToUser 在 Spring SockJS+Websocket 框架中是如何工作的。

在客户端,我们将连接为

stompClient.connect(login, password, callback())

这将导致连接请求带有登录名和密码的“Stomp 凭据”,例如可以看到如果我们处理 SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/

但我仍不清楚这是否是服务器端向队列发送操作中的“用户”:

 simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);

我能得到的最接近的方法是阅读此线程 Sending message to specific user on Spring Websocket,由 Thanh Nguyen Van 回答,但仍不清楚。

基本上我需要做的是为一些客户端订阅相同的主题,但在服务器上,向它们发送不同的数据。客户端可以提供用户标识符。

【问题讨论】:

您的问题找到解决方案了吗?我也面临同样的问题,正在寻找解决方案 您应该为每个连接配置弹簧安全性或使用@Siddharth 的未知用户解决方案,这也是我见过的最佳解决方案。 【参考方案1】:

我们知道我们可以使用客户端订阅的主题前缀从 stomp 服务器向客户端发送消息,例如/topic/hello。我们还知道我们可以向特定用户发送消息,因为 spring 提供了convertAndSendToUser(username, destination, message) API。它接受一个字符串用户名,这意味着如果我们以某种方式为每个连接都有一个唯一的用户名,我们应该能够向订阅主题的特定用户发送消息。

不太了解的是,这个用户名是从哪里来的?

此用户名是java.security.Principal 接口的一部分。 每个StompHeaderAccessorWebSocketSession 对象都有此主体的实例,您可以从中获取用户名。但是,根据我的实验,它不是自动生成的。 必须由服务器为每个会话手动生成。

要使用这个接口,你需要先实现它。

class StompPrincipal implements Principal 
    String name

    StompPrincipal(String name) 
        this.name = name
    

    @Override
    String getName() 
        return name
    

然后,您可以通过覆盖 DefaultHandshakeHandler 为每个连接生成唯一的StompPrincipal。您可以使用任何逻辑来生成用户名。这是一个使用 UUID 的潜在逻辑:

class CustomHandshakeHandler extends DefaultHandshakeHandler 
    // Custom class for storing principal
    @Override
    protected Principal determineUser(
        ServerHttpRequest request,
        WebSocketHandler wsHandler,
        Map<String, Object> attributes
    ) 
        // Generate principal with UUID as name
        return new StompPrincipal(UUID.randomUUID().toString())
    

最后,您需要配置您的 websocket 以使用您的自定义握手处理程序。

@Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) 
    stompEndpointRegistry
         .addEndpoint("/stomp") // Set websocket endpoint to connect to
         .setHandshakeHandler(new CustomHandshakeHandler()) // Set custom handshake handler
         .withSockJS() // Add Sock JS support

就是这样。现在,您的服务器已配置为为每个连接生成唯一的主体名称。它将作为StompHeaderAccessor 对象的一部分传递该主体,您可以通过连接事件侦听器、MessageMapping 函数等访问这些对象...

来自事件监听器:

@EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) 
    // Get Accessor
    StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())

来自消息映射 API

@MessageMapping('/hello')
protected void hello(SimpMessageHeaderAccessor sha, Map message) 
    // sha available in params

关于使用convertAndSendToUser(...) 的最后一点说明。向用户发送消息时,您将使用类似这样的内容

convertAndSendToUser(sha.session.principal.name, '/topic/hello', message)

但是,对于订阅客户端,您将使用

client.subscribe('/user/topic/hello', callback)

如果您为客户端订阅/topic/hello,您将只会收到广播消息。

【讨论】:

如果我没有@MessageMapping,从哪里访问userName。比如说我想在常规方法中使用convertAndSendToUser(...) 你能用convertAndSendToUser(...)填写sha.session.principal.name的来源吗 嗨@Siddharth,当您在类路径上没有 Spring Security 或者您仍然启用 Spring Security 时,这种方法是否有效? @SandeshaJ 当handleSessionConnectedEventin() 在地图或Redis 等缓存中触发时保存用户名,以便convertAndSendToUser 在发送时可以访问。 我无法从 sha 访问会话。我收到session cannot be resolved or is not a field 错误。【参考方案2】:

我没有做任何具体的配置,我可以这样做:

@MessageMapping('/hello')
protected void hello(Principal principal, Map message) 
    String username = principal.getName();

【讨论】:

不完全符合 OP 的要求,但对我有用。我想在@messagemapping 方法中提取发件人。在HttpHandshakeInterceptor 内部处理这个问题很困难,因为它的构造函数中有一个ServerHttpRequest 对象。 在CustomHandshakeHandler的determineUser中到底有没有办法访问Principal?【参考方案3】:

类似于 Wenneguen,我可以通过在 MessageMapping 方法中注入 Principal 来做到这一点

public void processMessageFromClient(@Payload String message, Principal principal) 

principal.getName() 的实现来自org.springframework.security.authentication.UsernamePasswordAuthenticationToken

【讨论】:

以上是关于在 SockJS+Spring Websocket 中,convertAndSendToUser 中的“用户”来自哪里?的主要内容,如果未能解决你的问题,请参考以下文章

在 SockJS+Spring Websocket 中,convertAndSendToUser 中的“用户”来自哪里?

websocket-spring 整合

Spring Boot SockJS应用例子

Spring Boot Websocket 在 SockJS 客户端测试中抛出“连接被拒绝”

我可以使用 Spring SockJs websocket 实现添加 cookie

Spring WebSocket 使用 SockJS 连接到不同的域