如何使用Spring WebSocket向STOMP客户端发送ERROR消息?
我使用Spring的STOMP而不是WebSocket实现,使用功能齐全的ActiveMQ代理。当用户 SUBSCRIBE
到某个主题时,在成功订阅之前,他们必须通过一些权限逻辑。我正在使用ChannelInterceptor来应用权限逻辑,如下所示:
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
}
}
当没有足够权限的客户尝试订阅受限制的主题时,他们实际上从未接收过来自主题BUT的任何消息也不会被通知拒绝其订阅的抛出的异常。相反,客户端将交还一个ActiveMQ代理一无所知的死定订阅。 (正常,充分许可的客户端与STOMP端点的交互和主题的工作方式与预期一致。)
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.)
我已尝试订阅 users / {subscribingUsername } / queue / errors
并且在成功连接后使用我的Java测试客户端只是简单的 users / queue / errors
但是我到目前为止无法从传递到客户端的服务器获得有关订阅异常的错误消息。这显然不太理想,因为客户从未被告知他们被拒绝访问。
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.
你不能只是扔来自 clientInboundChannel
的 MySubscriptionInterceptor
的异常,因为最后一个是 ExecutorSubscribableChannel
,因此是 async
,来自这些线程的任何异常都会在日志中结束,并且会向调用者重新抛出 - StompSubProtocolHandler.handleMessageFromClient
。
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
.
但你可以做的是像 clientOutboundChannel
,并像使用它一样这个:
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();
}