Как глобально обрабатывать исключение Spring WebSockets/Spring Messaging?

Вопрос
Есть ли способ глобально обработать Spring Messaging MessageDeliveryException, вызванный ошибкой (обычно недостаточно прав) в модуле Spring WebSocket?

Случай использования
Я внедрил Spring WebSockets поверх STOMP для поддержки соединения ws в своем веб-приложении. Чтобы защитить конечную точку веб-сокета, я создал перехватчик, который разрешает пользователю запускать сеанс STOMP во время STOMP CONNECT (как это предлагается в документации Spring здесь, в разделе 22.4.11):

@Component
public class StompMessagingInterceptor extends ChannelInterceptorAdapter {

    // Some code not important to the problem

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

        switch (headerAccessor.getCommand()) {
            // Authenticate STOMP session on CONNECT using jwt token passed as a STOMP login header - it's working great
            case CONNECT:
                authorizeStompSession(headerAccessor);
                break;
        }

        // Returns processed message
        return message;
    }

    // Another part of code not important for the problem
}

и включил конфигурацию spring-security-messaging, чтобы добавить некоторый детальный контроль над полномочиями при обмене сообщениями:

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
            .simpTypeMatchers(
                SimpMessageType.CONNECT,
                SimpMessageType.DISCONNECT,
                SimpMessageType.HEARTBEAT
            ).authenticated()
            .simpSubscribeDestMatchers("/queue/general").authenticated()
            .simpSubscribeDestMatchers("/user/queue/priv").authenticated()
            .simpDestMatchers("/app/general").authenticated()
            .simpDestMatchers("/user/*/queue/priv").hasAuthority("ADMIN")
            .anyMessage().denyAll();
    }

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

Прежде всего - эта конфигурация работает так, как ожидалось, проблема заключается в том, что когда во время связи через веб-сокет возникает какое-то исключение безопасности (скажем, пользователь без прав администратора пытается отправить сообщение на конечную точку "/user/{something}/queue/priv"), оно закончится в org.springframework.messaging.MessageDeliveryException поднимается и:

  • Полная трассировка стека исключений записывается в журнал моего сервера
  • Возврат кадра STOMP ERROR, содержащего часть трассировки стека в виде поля message.

Что я хотел бы сделать, так это поймать (если возможно глобально) DeliveryException, проверить, что вызвало это, и, соответственно, создать собственное сообщение для возврата в кадре STOMP ERROR (скажем, с некоторым кодом ошибки, например, просто 403 для имитации HTTP) и вместо выбрасывая исходное исключение, просто регистрируя некоторое предупреждение с помощью моего регистратора. Является ли это возможным?

Что я пробовал
При поиске решения я обнаружил, что некоторые люди используют @MessageExceptionHandler для перехвата исключений обмена сообщениями, в документации Spring 4.2.3 (это версия, которую я использую) упоминается только один раз здесь в 25.4.11 раздел. Я пытался использовать его так:

@Controller
@ControllerAdvice
public class WebSocketGeneralController {

    ...

    @MessageExceptionHandler
    public WebSocketMessage handleException(org.springframework.messaging.MessageDeliveryException e) {
        WebSocketMessage errorMessage = new WebSocketMessage();
        errorMessage.setMessage(e.getClass().getName());
        return errorMessage;
    }
}

но похоже, что метод ни разу не вызывается (пробовал ловить разные исключения, только Exception в том числе - безрезультатно). Что еще я должен изучить?


person Plebejusz    schedule 08.02.2018    source источник
comment
Та же проблема здесь ... Вы нашли решение?   -  person Migs    schedule 14.02.2020


Ответы (2)


Это не работает из-за исключения перехвата @ControllerAdvice из запроса, переданного сервлету диспетчера. Когда вы защищаете свою конечную точку, и кто-то делает несанкционированный запрос, он не проходит через диспетчерский сервлет. Запрос перехватывается перехватчиками spring.

person Pulkownik    schedule 08.02.2018
comment
Я вижу, и это на самом деле полезно знать, но это не решает проблему - поскольку в соответствии с этим нет способа перехватить исключение безопасности веб-сокета на уровне контроллера, поэтому также нельзя изменить возвращаемый кадр STOMP ERROR, тогда мой вопрос о где именно и как это можно сделать остается открытым. - person Plebejusz; 09.02.2018

@ControllerAdvice и @MessageExceptionHandler работают на уровне бизнес-логики (как @MessageMapping или SimpMessagingTemplate).

Для обработки исключений STOMP вам необходимо установить обработчик ошибок STOMP в реестре STOMP:

@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {

    override fun configureMessageBroker(registry: MessageBrokerRegistry) {
        // ...
    }

    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/ws")

        // Handle exceptions in interceptors and Spring library itself.
        // Will terminate a connection and send ERROR frame to the client.
        registry.setErrorHandler(object : StompSubProtocolErrorHandler() {
            override fun handleInternal(
                errorHeaderAccessor: StompHeaderAccessor,
                errorPayload: ByteArray,
                cause: Throwable?,
                clientHeaderAccessor: StompHeaderAccessor?
            ): Message<ByteArray> {
                errorHeaderAccessor.message = null
                val message = "..."
                return MessageBuilder.createMessage(message.toByteArray(), errorHeaderAccessor.messageHeaders)
            }
        })
    }
}

person Alexander Biryukov    schedule 05.11.2020