如何使用Spring WebSocket向STOMP客户端发送ERROR消息?(How to send ERROR message to STOMP clients with Spring WebSocket?)

1728 IT屋

我使用Spring的STOMP而不是WebSocket实现,使用功能齐全的ActiveMQ代理。当用户 SUBSCRIBE 到某个主题时,在成功订阅之前,他们必须通过一些权限逻辑。我正在使用ChannelInterceptor来应用权限逻辑,如下所示:



WebSocketConfig.java:



  @EnableWebSocketMessageBroker 
公共类WebSocketConfig扩展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:



 公共类WebSocketSecurityConfig扩展AbstractSecurityWebSocketMessageBrokerConfigurer {

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

}


MySubscriptionInterceptor.java:



 公共类MySubscriptionInterceptor扩展了ChannelInterceptorAdapter {

@Override
public Message< ;?> preSend(消息<?>消息,MessageChannel频道){

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

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

返回消息;
}

private void checkPermissions(校长){
//应用权限逻辑
//抛出异常权限不足
}
}


当没有足够权限的客户尝试订阅受限制的主题时,他们实际上从未接收过来自主题BUT的任何消息也不会被通知拒绝其订阅的抛出的异常。相反,客户端将交还一个ActiveMQ代理一无所知的死定订阅。 (正常,充分许可的客户端与STOMP端点的交互和主题的工作方式与预期一致。)



我已尝试订阅 users / {subscribingUsername } / queue / errors 并且在成功连接后使用我的Java测试客户端只是简单的 users / queue / errors 但是我到目前为止无法从传递到客户端的服务器获得有关订阅异常的错误消息。这显然不太理想,因为客户从未被告知他们被拒绝访问。


解决方案

你不能只是扔来自 clientInboundChannel 的 MySubscriptionInterceptor 的异常,因为最后一个是 ExecutorSubscribableChannel ,因此是 async ,来自这些线程的任何异常都会在日志中结束,并且会向调用者重新抛出 - StompSubProtocolHandler.handleMessageFromClient 。



但你可以做的是像 clientOutboundChannel ,并像使用它一样这个:



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

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


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



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

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

I am using Spring's STOMP over WebSocket implementation with a full-featured ActiveMQ broker. When users SUBSCRIBE to a topic, there is some permissions logic that they must pass through before being successfully subscribed. I am using a ChannelInterceptor to apply the permissions logic, as configured below:

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

When clients who do not have adequate permissions attempt to subscribe to a restricted topic, they never actually receive any messages from the topic BUT are also not notified of the exception that was thrown which rejected their subscription. Instead, the client is handed back a dead subscription that the ActiveMQ broker knows nothing about. (Normal, adequately-permissioned client interactions with the STOMP endpoint and topics work just as expected.)

I have tried subscribing to users/{subscribingUsername}/queue/errors and just plain users/queue/errors with my Java test client after it is successfully connected, but I have thus far been unable to get sort of error message about the subscription exception from the server delivered to the client. This is obviously less than ideal since clients are never notified that they've been denied access.

解决方案

You can't just throw exception from the MySubscriptionInterceptor on the clientInboundChannel, because the last one is ExecutorSubscribableChannel, therefore is async and any exceptions from those threads are end up in the logs with any re-throw to the caller - StompSubProtocolHandler.handleMessageFromClient.

But what you can do there is something like clientOutboundChannel and use it like this:

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

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

Another option to consider is Annotation mapping:

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

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

本文地址:IT屋 » 如何使用Spring WebSocket向STOMP客户端发送ERROR消息?

相关文章: