«1. Введение

В этом руководстве мы опишем, как использовать Spring WebSockets для отправки сообщений STOMP одному пользователю. Это важно, потому что иногда мы не хотим транслировать каждое сообщение каждому пользователю. Кроме того, мы покажем, как отправлять эти сообщения безопасным способом.

Для ознакомления с WebSockets ознакомьтесь с этим замечательным руководством о том, как приступить к работе. И, чтобы глубже погрузиться в безопасность, ознакомьтесь с этой статьей, чтобы защитить свою реализацию WebSockets.

2. Очереди, темы и конечные точки

Есть три основных способа сказать, куда отправляются сообщения и как они подписываются, используя Spring WebSockets и STOMP:

  1. Topics – common conversations or chat topics open to any client or user
  2. Queues – reserved for specific users and their current sessions
  3. Endpoints – generic endpoints

Теперь давайте быстро рассмотрим пример. контекстный путь для каждого:

    «/topic/movies» «/user/queue/specific-user» «/secured/chat»

Важно отметить, что мы должны использовать очереди для отправки сообщения определенным пользователям, так как темы и конечные точки не поддерживают эту функцию.

3. Конфигурация

Теперь давайте узнаем, как настроить наше приложение, чтобы мы могли отправлять сообщения определенному пользователю:

public class SocketBrokerConfig extends 
  AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/secured/user/queue/specific-user");
        config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
        config.setUserDestinationPrefix("/secured/user");
    }

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

Давайте обязательно включим пункт назначения пользователя, так как он определяет, какие конечные точки зарезервированы. для одиночных пользователей.

Мы также добавляем префикс «/secured» ко всем нашим очередям и адресатам пользователей, чтобы они требовали аутентификации. Для незащищенных конечных точек мы можем отказаться от префикса «/secured» (в результате других наших настроек безопасности).

С точки зрения pom.xml никаких дополнительных зависимостей не требуется.

4. Сопоставления URL-адресов

Мы хотим, чтобы наш клиент подписался на очередь, используя сопоставление URL-адресов, соответствующее следующему шаблону:

"/user/queue/updates"

Это сопоставление будет автоматически преобразовано UserDestinationMessageHandler в user-session- конкретный адрес.

Например, если у нас есть пользователь с именем «user123», соответствующий адрес будет таким:

"/queue/updates-user123"

На стороне сервера мы отправим ответ, специфичный для пользователя, используя следующий шаблон сопоставления URL-адресов: ~ ~~

"/user/{username}/queue/updates"

Это тоже будет преобразовано в правильное сопоставление URL-адресов, на которое мы уже подписались на стороне клиента.

Таким образом, мы видим, что основные ингредиенты здесь двоякие:

  1. Prepend our specified User Destination Prefix (configured in AbstractWebSocketMessageBrokerConfigurer).
  2. Use “/queue” somewhere within the mapping.

В следующем разделе мы рассмотрим, как именно это сделать.

5. Вызов convertAndSendToUser()

Мы можем нестатически вызвать convertAndSendToUser() из SimpMessagingTemplate или SimpMessageSendingOperations:

@Autowired
private SimpMessagingTemplate simpMessagingTemplate;

@MessageMapping("/secured/room") 
public void sendSpecific(
  @Payload Message msg, 
  Principal user, 
  @Header("simpSessionId") String sessionId) throws Exception { 
    OutputMessage out = new OutputMessage(
      msg.getFrom(), 
      msg.getText(),
      new SimpleDateFormat("HH:mm").format(new Date())); 
    simpMessagingTemplate.convertAndSendToUser(
      msg.getTo(), "/secured/user/queue/specific-user", out); 
}

Вы могли заметить:

@Header("simpSessionId") String sessionId

Аннотация @Header позволяет получить доступ к заголовки, предоставляемые входящим сообщением. Например, мы можем получить текущий идентификатор сеанса без необходимости использования сложных перехватчиков. Точно так же мы можем получить доступ к текущему пользователю через Principal.

Важно отметить, что подход, который мы используем в этой статье, обеспечивает более широкие возможности настройки по сравнению с аннотацией @sendToUser в отношении сопоставлений URL-адресов. Чтобы узнать больше об этой аннотации, ознакомьтесь с этой замечательной статьей.

На стороне клиента мы будем использовать connect() в JavaScript для инициализации экземпляра SockJS и подключения к нашему серверу WebSocket с помощью STOMP:

var socket = new SockJS('/secured/room'); 
var stompClient = Stomp.over(socket);
var sessionId = "";

stompClient.connect({}, function (frame) {
    var url = stompClient.ws._transport.url;
    url = url.replace(
      "ws://localhost:8080/spring-security-mvc-socket/secured/room/",  "");
    url = url.replace("/websocket", "");
    url = url.replace(/^[0-9]+\//, "");
    console.log("Your current session is: " + url);
    sessionId = url;
}

Мы также получаем доступ к предоставленному идентификатору сеанса и добавляем его к «secured /room — сопоставление URL-адресов. Это дает нам возможность динамически и вручную задавать очередь подписки для конкретного пользователя:

stompClient.subscribe('secured/user/queue/specific-user' 
  + '-user' + that.sessionId, function (msgOut) {
     //handle messages
}

После того, как все настроено, мы должны увидеть:

И в консоли нашего сервера:

6. Заключение ~ ~~ Посетите официальный блог Spring и официальную документацию для получения дополнительной информации по этой теме.

Как всегда, образцы кода, использованные в этой статье, доступны на GitHub.

«

As always, the code samples used in this article is available over on GitHub.