如何全局处理Spring WebSockets/Spring Messaging异常?

9

问题
有没有一种方法可以全局处理由Spring WebSocket模块中的错误(通常是权限不足)引起的Spring Messaging MessageDeliveryException

用例
我已经实现了基于STOMP的Spring WebSockets,以支持我的Web应用程序中的ws连接。为了保护websocket端点,我创建了一个拦截器,在STOMP CONNECT时间授权用户启动STOMP会话(如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;
    }
}

首先 - 这个配置按预期工作,问题在于当websocket通信过程中发生某些安全异常时(例如没有管理员权限的用户尝试在“/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 - 没有结果)。我还应该查看什么?


这里也有同样的问题...你找到解决方案了吗? - Migs
2个回答

2

@ControllerAdvice@MessageExceptionHandler@MessageMappingSimpMessagingTemplate 类似,属于业务逻辑层面。

要处理 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)
            }
        })
    }
}


0

它无法工作是因为@ControllerAdvice从传递给调度程序servlet的请求中捕获异常。当您保护端点并且有人进行未经授权的请求时,它不会通过调度程序servlet。该请求被Spring拦截器捕获。


我明白了,这个信息很有用,但它并没有解决问题 - 因为根据这个信息,在控制器级别无法捕获WebSocket安全异常,因此也无法在那里修改返回的STOMP ERROR帧,那么我的问题关于在哪里以及如何完成仍然存在。 - Plebejusz

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