You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何全局处理Spring WebSocket中Spring Messaging的MessageDeliveryException?

当然可以全局处理这类MessageDeliveryException!我之前在基于STOMP的Spring WebSocket项目里也遇到过类似的权限异常处理需求,下面给你分享几种实用的方案:

方案1:监听MessageDeliveryExceptionEvent全局事件

Spring Messaging会在消息投递失败时发布MessageDeliveryExceptionEvent事件,我们可以通过实现ApplicationListener来全局捕获并处理:

@Component
public class StompDeliveryErrorListener implements ApplicationListener<MessageDeliveryExceptionEvent> {

    private static final Logger log = LoggerFactory.getLogger(StompDeliveryErrorListener.class);
    private final SimpMessagingTemplate messagingTemplate;

    public StompDeliveryErrorListener(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @Override
    public void onApplicationEvent(MessageDeliveryExceptionEvent event) {
        MessageDeliveryException exception = event.getException();
        Message<?> failedMessage = event.getFailedMessage();
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(failedMessage);

        // 重点处理权限不足的场景
        if (exception.getCause() instanceof AccessDeniedException) {
            String sessionId = headerAccessor.getSessionId();
            String targetDestination = headerAccessor.getDestination();
            
            // 给客户端发送权限错误通知(需客户端提前订阅/user/queue/errors)
            messagingTemplate.convertAndSendToUser(
                sessionId,
                "/queue/errors",
                String.format("权限不足,无法访问资源:%s", targetDestination)
            );
            
            log.warn("会话[{}]访问[{}]时权限不足", sessionId, targetDestination);
        } else {
            // 处理其他类型的投递异常
            log.error("消息投递失败,会话ID:{},目标地址:{}",
                      headerAccessor.getSessionId(),
                      headerAccessor.getDestination(),
                      exception);
        }
    }
}
方案2:自定义ChannelInterceptor拦截通道异常

消息在客户端入站通道传递时出现的异常,可以通过自定义拦截器捕获,适合在消息处理链路早期干预:

@Component
public class GlobalStompErrorInterceptor implements ChannelInterceptor {

    private static final Logger log = LoggerFactory.getLogger(GlobalStompErrorInterceptor.class);
    private final SimpMessagingTemplate messagingTemplate;

    public GlobalStompErrorInterceptor(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @Override
    public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
        if (!sent) {
            StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
            Throwable error = headers.getError();
            
            if (error instanceof MessageDeliveryException) {
                MessageDeliveryException deliveryEx = (MessageDeliveryException) error;
                if (deliveryEx.getCause() instanceof AccessDeniedException) {
                    String sessionId = headers.getSessionId();
                    // 发送错误通知或执行其他处理逻辑
                    messagingTemplate.convertAndSendToUser(
                        sessionId,
                        "/queue/errors",
                        "操作被拒绝:您没有足够的权限"
                    );
                }
                log.error("通道消息投递失败,会话ID:{}", headers.getSessionId(), error);
            }
        }
    }
}

记得在WebSocket配置类中注册这个拦截器:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Autowired
    private GlobalStompErrorInterceptor errorInterceptor;

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(errorInterceptor);
    }

    // 其他WebSocket配置(如消息代理、端点等)...
}
方案3:结合Spring Security的权限异常处理器

既然你已经使用了spring-security-messaging,可以自定义AccessDeniedHandler来专门处理权限不足的场景:

@Component
public class StompAccessDeniedHandler implements AccessDeniedHandler {

    private final SimpMessagingTemplate messagingTemplate;

    public StompAccessDeniedHandler(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    @Override
    public void handle(Message<?> message, AccessDeniedException accessDeniedException) throws MessagingException {
        StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
        String sessionId = headers.getSessionId();
        
        // 发送权限错误提示
        messagingTemplate.convertAndSendToUser(
            sessionId,
            "/queue/errors",
            accessDeniedException.getMessage()
        );
        
        // 可选:直接断开违规会话
        // headers.setSessionDisconnectMessage(SimpMessageHeaderAccessor.create(SimpMessageType.DISCONNECT));
        // messagingTemplate.send(headers.getMessageHeaders(), null);
    }
}

然后在Spring Security的WebSocket配置中注册这个处理器:

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Autowired
    private StompAccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
            .simpDestMatchers("/topic/admin/**").hasRole("ADMIN")
            .anyMessage().authenticated();
    }

    @Override
    protected AccessDeniedHandler accessDeniedHandler() {
        return accessDeniedHandler;
    }

    // 其他Security配置...
}

几个注意点:

  • 客户端需要提前订阅对应的错误队列(比如/user/queue/errors)才能接收全局推送的错误通知
  • 日志要尽可能详细,包含会话ID、目标地址、异常栈信息,方便后续排查问题
  • 如果需要断开违规会话,可以通过SimpMessagingTemplate发送DISCONNECT指令

内容的提问来源于stack exchange,提问作者Plebejusz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:13:14