如何使用Spring WebSocket向STOMP客户端发送错误消息?

16

我正在使用Spring的STOMP over WebSocket实现,并配合一个功能齐全的ActiveMQ代理。当用户订阅主题时,他们必须通过某些权限逻辑才能成功订阅。我正在使用ChannelInterceptor来应用权限逻辑,如下所配置:

WebSocketConfig.java:

@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/stomp")
      .setAllowedOrigins("*")
      .withSockJS();
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableStompBrokerRelay("/topic", "/queue")
      .setRelayHost("relayhost.mydomain.com")
      .setRelayPort(61613);
  }

  @Override
  public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.setInterceptors(new MySubscriptionInterceptor());
  }


}

WebSocketSecurityConfig.java:

public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

  @Override
  protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
    messages
      .simpSubscribeDestMatchers("/stomp/**").authenticated()
      .simpSubscribeDestMatchers("/user/queue/errors").authenticated()
      .anyMessage().denyAll();
  }

}

MySubscriptionInterceptor.java:

public class MySubscriptionInterceptor extends ChannelInterceptorAdapter {

  @Override
  public Message<?> preSend(Message<?> message, MessageChannel channel) {

    StompHeaderAccessor headerAccessor= StompHeaderAccessor.wrap(message);
    Principal principal = headerAccessor.getUser();

    if (StompCommand.SUBSCRIBE.equals(headerAccessor.getCommand())) {
      checkPermissions(principal);
    }

    return message;
  }

  private void checkPermissions(Principal principal) {
    // apply permissions logic
    // throw Exception permissions not sufficient
  }
}
当没有足够权限的客户端尝试订阅受限制的主题时,它们实际上从主题中根本没有接收到任何消息,但也没有收到拒绝他们的订阅的异常通知。相反,客户端会收到一个ActiveMQ代理不知道的无效订阅。(正常的、有适当权限的客户端与STOMP端点和主题的交互正常工作。)
在成功连接后,我已经尝试使用我的Java测试客户端订阅users/{subscribingUsername}/queue/errors和普通的users/queue/errors,但迄今为止我无法从服务器传递给客户端关于订阅异常的任何错误消息。这显然不太理想,因为客户端从未收到被拒绝访问的通知。
1个回答

11

您不能仅仅从MySubscriptionInterceptorclientInboundChannel上抛出异常,因为后者是ExecutorSubscribableChannel,因此是async的,来自这些线程的任何异常最终都会以日志形式记录,并且不会重新抛出给调用者- StompSubProtocolHandler.handleMessageFromClient

但是你可以在那里做类似于clientOutboundChannel并像这样使用它:

StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.ERROR);
headerAccessor.setMessage(error.getMessage());

clientOutboundChannel.send(MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders()));

另一个需要考虑的选项是注释映射:

    @SubscribeMapping("/foo")
    public void handleWithError() {
        throw new IllegalArgumentException("Bad input");
    }

    @MessageExceptionHandler
    @SendToUser("/queue/error")
    public String handleException(IllegalArgumentException ex) {
        return "Got error: " + ex.getMessage();
    }

1
感谢@Artem的帮助,我已经尝试了。不幸的是,当@SubscribeMapping方法中抛出异常并由@MessageExceptionHandler方法处理时,确实会向客户端的错误队列发送消息,但他们的订阅仍然转发到代理,并且客户端仍然从主题接收后续消息。 - hartz89
1
因此,请尝试从@MessageExceptionHandler直接使用send将消息发送到clientOutboundChannel - Artem Bilan
@ArtemBilan,您能否解释一下如何获取OutboundChannel?我提出了一个问题:https://dev59.com/xJrga4cB1Zd3GeqPvP5P - onkami

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