【问题标题】:How to globally handle Spring WebSockets/Spring Messaging exception?如何全局处理 Spring WebSockets/Spring Messaging 异常?
【发布时间】:2018-07-19 05:14:38
【问题描述】:

问题
有没有办法全局处理 Spring WebSocket 模块中的错误(通常权限不足)导致的 Spring Messaging MessageDeliveryException

用例
我已经通过 STOMP 实现了 Spring WebSockets 以支持我的 web 应用程序中的 ws 连接。为了保护 websocket 端点,我创建了拦截器,授权用户在 STOMP CONNECT 时间启动 STOMP 会话(如 Spring 文档 here in 22.4.11 section 中所建议):

@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(我使用的版本)文档只提到了一次here in 25.4.11 section。我试着这样使用它:

@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 包括 - 没有结果)。我还应该研究什么?

【问题讨论】:

  • 同样的问题...您找到解决方案了吗?

标签: java spring spring-security spring-websocket spring-messaging


【解决方案1】:

@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)
            }
        })
    }
}

【讨论】:

    【解决方案2】:

    由于 @ControllerAdvice 从通过调度程序 servlet 的请求中捕获异常,它不起作用。当您保护端点并且有人发出未经授权的请求时,它不会通过调度程序 servlet。请求被 Spring 拦截器捕获。

    【讨论】:

    • 我明白了,这实际上很高兴知道,但它并不能解决问题 - 因为根据这个没有办法在控制器级别捕获 websocket 安全异常,因此也不能返回 STOMP ERROR 帧在那里进行修改,然后我关于在哪里以及如何完成的问题仍然悬而未决。
    猜你喜欢
    • 2021-08-19
    • 2016-11-18
    • 2013-04-02
    • 2018-11-19
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多