Вопрос
Есть ли способ глобально обработать 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
в том числе - безрезультатно). Что еще я должен изучить?