Проверяйте авторизацию при отправке сообщения конкретному пользователю с помощью STOMP и WebSocket в Spring

Я разрабатываю систему уведомлений в реальном времени в Spring 4, используя встроенный брокер сообщений и STOMP через WebSocket. сильный>.

Я хотел бы иметь возможность отправлять сообщения конкретному пользователю в соответствии с его именем пользователя. Для достижения этой цели я использую метод convertAndSendToUser класса org.springframework.messaging.simp.SimpMessagingTemplate следующим образом:

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, ...) 

Последний пункт звучит странно...

Предположим, что я зарегистрирован как 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

person vdenotaris    schedule 03.09.2014    source источник


Ответы (2)


Я нашел решение.

Прежде всего, важно знать, что канал /user уже управляется Spring STOMP, и, кстати, регистрация не требуется.

So:

@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, выбранным контекстом безопасности.

person vdenotaris    schedule 03.09.2014
comment
Поздравляем с решением. Знаете ли вы, будет ли аналогичная версия с использованием аннотации @SendToUser? - person hawaii; 29.09.2014
comment
Это должно быть то же самое. Пробовали ли вы использовать @SendToUser с /user/queue/horray в качестве пункта назначения? - person vdenotaris; 29.09.2014
comment
да. По какой-то странной причине это не сработало (клиент так и не получил обратный вызов подписки, даже если метод был вызван успешно). Мое решение состояло в том, чтобы добавить собственный заголовок (toUser) и отправить сообщение с помощью SimpMessagingTemplate.convertAndSendToUser. Я извлекаю заголовки, добавляя параметр сообщения Message‹?› в методе контроллера. Пользователь подписан на '/user/topic/chat'. Если вы подпишитесь на '/user/{username}/topic/chat', вы не будете получать сообщения (что и предполагалось). Кроме того, нет необходимости включать /user в качестве брокера. - person hawaii; 29.09.2014
comment
Мой конфиг почти такой же, как у вас (я использую очередь/ответ вместо очереди/ужас), но мой консольный вывод пуст. Вы пропустили какие-то важные моменты в своем посте или это все? Не могли бы вы опубликовать полную конфигурацию? - person Baurzhan; 15.12.2015
comment
Привет, @vdenotaris. Я заметил ваш запланированный метод публикации сообщений: @Scheduled(fixedDelay=5000) public void sendMessages(Principal Principal) messagingTemplate .convertAndSendToUser(principal.getName(), /queue/horray, Horray, + Principal.getName() +!) ; } Запланированные методы Spring не работают с параметрами. Как я могу получить Принципала внутри запланированного метода? Заранее спасибо! - person Samir; 28.02.2016
comment
Любой метод, аннотированный @Scheduled, не может принимать никаких аргументов. @Scheduled(fixedDelay=5000) public void sendMessages(Principal Principal){} это не сработает. - person Ashish Bhosle; 12.07.2017
comment
Этот вопрос был опубликован и принят 3 года назад. Пожалуйста, прекратите некропостинг! - person vdenotaris; 12.07.2017
comment
@vdenotaris Я не использую безопасность Spring :-(, как мне получить здесь пользователя messagingTemplate .convertAndSendToUser(?, "/queue/horray", "Horray, ?"); - person Shantaram Tupe; 02.12.2017

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

на сервере:

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

И клиент довольно простой

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

Возможно, это не самый чистый способ, но он работает, и без аутентификации я не мог использовать канал /user.

person Alexandre Langlais    schedule 22.06.2015
comment
В моем случае. Если два пользователя вошли в один и тот же browser, их sessionId будет одинаковым, поэтому каждый может видеть подписки других, это должно быть сделано для user_id или user_name. .. - person Shantaram Tupe; 02.12.2017
comment
@ShantaramTupe два пользователя вошли в один и тот же браузер, ИМХО, недопустимый случай. В этом случае вам придется переключать сеансы внутри. Опять же, практически и в конечном итоге это будет не несколько пользователей, а один пользователь с несколькими учетными записями. (например, вы могли бы подумать о Gmail). - person Romeo Sierra; 31.12.2018