Spring WebSocket 发送消息给特定用户

18

我为我的Spring Web应用程序添加了基于自定义令牌的身份验证,并将其扩展到Spring WebSocket,如下所示。

public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/queue");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").setAllowedOrigins("*").withSockJS();
    }

    @Override
      public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.setInterceptors(new ChannelInterceptorAdapter() {

            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {

                StompHeaderAccessor accessor =
                    MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    String jwtToken = accessor.getFirstNativeHeader("Auth-Token");
                        if (!StringUtils.isEmpty(jwtToken)) {
                            Authentication auth = tokenService.retrieveUserAuthToken(jwtToken);
                            SecurityContextHolder.getContext().setAuthentication(auth);
                            accessor.setUser(auth);
                            //for Auth-Token '12345token' the user name is 'user1' as auth.getName() returns 'user1'
                        }
                }

                return message;
            }
        });
      }
}

连接到socket的客户端代码为:

var socket = new SockJS('http://localhost:8080/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({'Auth-Token': '12345token'}, function (frame) {
        stompClient.subscribe('/user/queue/greetings', function (greeting) {
            alert(greeting.body);
        });
    });

我从我的控制器发送消息如下:

messagingTemplate.convertAndSendToUser("user1", "/queue/greetings", "Hi User1");

对于身份验证令牌12345token,用户名是user1。但是当我向user1发送消息时,在客户端端没有接收到。这里有什么我忽略了的吗?

2个回答

30

在您的Websocket控制器中,您应该像这样做:

@Controller
public class GreetingController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @MessageMapping("/hello")
    public void greeting(Principal principal, HelloMessage message) throws  Exception {
        Greeting greeting = new Greeting();
        greeting.setContent("Hello!");
        messagingTemplate.convertAndSendToUser(message.getToUser(), "/queue/reply", greeting);
    }
}
在客户端方面,您的用户应该订阅主题/user/queue/reply。同时您还需要添加一些目标前缀:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/queue" ,"/user");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }
/*...*/
}
当您的服务器在/app/hello队列收到一条消息时,应向您dto中的用户发送一条消息。用户必须等于用户的主体。
我认为您的代码中唯一的问题是"/user"不在您的目标前缀中。您的问候消息被阻止,因为您将它们发送到以/user开头的队列,并且该前缀未注册。
您可以在 git 存储库中检查源代码: https://github.com/simvetanylen/test-spring-websocket 希望它能正常工作!

1
问题在于,我希望消息只发送给特定的用户。如果我使用@SendTo("/user/queue/greetings"),应用程序如何知道我要将消息发送给哪个用户。另外,我尝试了你建议的方法,但客户端没有收到任何通知。 - BiJ
我已经完成了这个。我会尝试找到我的代码并在这里发布。 - Oreste Viron
这将是一个巨大的帮助。 - BiJ
非常感谢,这帮了我很多忙。由于我使用的是Spring Boot 1.5.2.RELEASE和版本0.0.1-SNAPSHOT,所以事情一直没有进展。 - BiJ
你不觉得在你的 function sendName() 这里 中暴露了 subscriber/user 吗?任何人都可以猜到 Subscriber,例如 John, Harry ... etc - Shantaram Tupe
显示剩余3条评论

6
在我的上一个项目中,我向一个特定的用户发送了消息;具体而言,我写下了以下内容: 客户端:
function stompConnect(notificationTmpl) 
{
    var socket = new SockJS('/comm-svr');
    stompClient = Stomp.over(socket);
    var theUserId 
    stompClient.connect({userId:theUserId}, function (frame) {
            debug('Connected: ' + frame);
            stompClient.subscribe('/topic/connect/'+theUserId, function (data)                   {
//Handle data
              } 
        });
}

服务器端

Spring Websocket 监听器:

@Component
public class WebSocketSessionListener
{
    private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionListener.class.getName());
    private List<String> connectedClientId = new ArrayList<String>();

    @EventListener
    public void connectionEstablished(SessionConnectedEvent sce)
    {
        MessageHeaders msgHeaders = sce.getMessage().getHeaders();
        Principal princ = (Principal) msgHeaders.get("simpUser");
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(sce.getMessage());
        List<String> nativeHeaders = sha.getNativeHeader("userId");
        if( nativeHeaders != null )
        {
            String userId = nativeHeaders.get(0);
            connectedClientId.add(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
        else
        {
            String userId = princ.getName();
            connectedClientId.add(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
    }

    @EventListener
    public void webSockectDisconnect(SessionDisconnectEvent sde)
    {
        MessageHeaders msgHeaders = sde.getMessage().getHeaders();
        Principal princ = (Principal) msgHeaders.get("simpUser");
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(sde.getMessage());
        List<String> nativeHeaders = sha.getNativeHeader("userId");
        if( nativeHeaders != null )
        {
            String userId = nativeHeaders.get(0);
            connectedClientId.remove(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
        else
        {
            String userId = princ.getName();
            connectedClientId.remove(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
    }

    public List<String> getConnectedClientId()
    {
        return connectedClientId;
    }
    public void setConnectedClientId(List<String> connectedClientId)
    {
        this.connectedClientId = connectedClientId;
    }
}

Spring WebSocket消息发送器:

@Autowired
    private SimpMessagingTemplate msgTmp;
    private void propagateDvcMsg( WebDeviceStatusInfo device )
    {
        String msg = "";
        String userId =((Principal)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getName()
        msgTmp.convertAndSend("/topic/connect"+userId, msg);
    }

希望这对你有用


谢谢。我今天会尝试,并告诉你结果的。只有一个问题,如何使用AbstractWebSocketMessageBrokerConfigurer配置套接字以适用于这种方法? - BiJ
在我的情况下,msgHeaders.get("simpUser") 为 null。 - x__dos
将客户端ID列表存储在字段变量中是否线程安全? - Mr Davron

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