Spring WebSocket Stomp 异常处理
Spring WebSocket Stomp Exception Handling
我正在尝试使用 Websockets 和 STOMP 1.2。我想在 CONNECT 框架上使用 JWT 对用户进行身份验证,如果授权无效,则显示 return 错误消息。这是通道拦截器
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{
// ... Removed for readability
@Override
public void configureClientInboundChannel(ChannelRegistration registration)
{
registration.interceptors(new ChannelInterceptor()
{
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel)
{
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand()))
{
String token = accessor.getFirstNativeHeader(authorizationHeader);
if (token != null)
{
Authentication authentication = tokenUtil.getAuthentication(resolveToken(token));
accessor.setUser(authentication);
}
else
{
throw new UnauthorizedException();
}
}
return message;
}
});
}
}
这样错误栈会有
Failed to send client message to application via MessageChannel in session a63cf95f-4a5b-ef34-b74b-3264ec6dd61f. Sending STOMP ERROR to client.
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is com.example.error.exception.UnauthorizedException: You are not authorized. Please log in.
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:146) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:122) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:284) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:324) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:113) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access[=12=]0(StandardWebSocketHandlerAdapter.java:42) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onMessage(StandardWebSocketHandlerAdapter.java:84) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onMessage(StandardWebSocketHandlerAdapter.java:81) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
Caused by: com.example.error.exception.UnauthorizedException: You are not authorized. Please log in.
at com.exampleconfig.WebSocketConfig.preSend(WebSocketConfig.java:74) ~[classes/:na]
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:178) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:132) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 28 common frames omitted
并且类似的错误将被发送到客户端连接将被关闭。
但是连接关闭后,其他堆栈跟踪被写为输出:
2018-11-11 01:06:48.077 DEBUG 7259 --- [nio-8080-exec-2] s.w.s.h.LoggingWebSocketHandlerDecorator : StandardWebSocketSession[id=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f, uri=/api/ws] closed with CloseStatus[code=1000, reason=null]
2018-11-11 01:06:48.077 DEBUG 7259 --- [nio-8080-exec-2] o.s.w.s.m.SubProtocolWebSocketHandler : Clearing session a63cf95f-4a5b-ef34-b74b-3264ec6dd61f
2018-11-11 01:06:48.084 DEBUG 7259 --- [nio-8080-exec-2] org.springframework.web.SimpLogging : Detected unsent DISCONNECT message. Processing anyway.
2018-11-11 01:06:48.085 DEBUG 7259 --- [nio-8080-exec-2] org.springframework.web.SimpLogging : Processing DISCONNECT session=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f
2018-11-11 01:06:48.086 DEBUG 7259 --- [tboundChannel-1] o.s.w.s.m.SubProtocolWebSocketHandler : No session for GenericMessage [payload=byte[0], headers={simpMessageType=DISCONNECT_ACK, simpDisconnectMessage=GenericMessage [payload=byte[0], headers={simpMessageType=DISCONNECT, stompCommand=DISCONNECT, simpSessionAttributes={org.springframework.messaging.simp.SimpAttributes.COMPLETED=true}, simpSessionId=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f}], simpSessionId=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f}]
2018-11-11 01:06:48.087 WARN 7259 --- [nio-8080-exec-2] w.s.h.ExceptionWebSocketHandlerDecorator : Unhandled exception after connection closed for ExceptionWebSocketHandlerDecorator [delegate=LoggingWebSocketHandlerDecorator [delegate=SubProtocolWebSocketHandler[StompSubProtocolHandler[v10.stomp, v11.stomp, v12.stomp]]]]
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:146) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:122) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.StompSubProtocolHandler.afterSessionEnded(StompSubProtocolHandler.java:611) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.clearSession(SubProtocolWebSocketHandler.java:516) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.afterConnectionClosed(SubProtocolWebSocketHandler.java:385) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.afterConnectionClosed(WebSocketHandlerDecorator.java:85) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.afterConnectionClosed(LoggingWebSocketHandlerDecorator.java:72) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.afterConnectionClosed(ExceptionWebSocketHandlerDecorator.java:78) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onClose(StandardWebSocketHandlerAdapter.java:144) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.apache.tomcat.websocket.WsSession.fireEndpointOnClose(WsSession.java:535) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsSession.onClose(WsSession.java:513) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processDataControl(WsFrameBase.java:347) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
Caused by: org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.1.1.RELEASE.jar:5.1.1.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-5.1.1.RELEASE.jar:5.1.1.RELEASE]
at org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor.preSend(ChannelSecurityInterceptor.java:69) ~[spring-security-messaging-5.1.1.RELEASE.jar:5.1.1.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:178) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:132) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 26 common frames omitted
我不知道 AccessDeniedException
从哪里来,我该如何处理。
如果我不在 preSend
方法中抛出 UnauthorizedException
,客户端将被连接,当它尝试发送内容时,它将收到 AccessDeniedException
,因为所有端点都需要经过身份验证的用户,然后关闭连接并打印几次 AccessDeniedException
的堆栈跟踪。
我在这里做错了什么,处理这种用例的正确方法是什么?
作为 AccessDenied 异常的解决方案,我没有配置为允许 DISCONNECT 消息通过安全。这是我现在的配置:
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer
{
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages)
{
messages
.simpTypeMatchers(SimpMessageType.CONNECT,
SimpMessageType.DISCONNECT, SimpMessageType.OTHER).permitAll()
.anyMessage().authenticated();
}
}
此外,我覆盖了 StompSubProtocolErrorHandler
,因此我可以向我的客户发送有关错误的自定义消息。
示例如下:
@Override
public Message<byte[]> handleClientMessageProcessingError(Message<byte[]>clientMessage, Throwable ex)
{
Throwable exception = ex;
if (exception instanceof MessageDeliveryException)
{
exception = exception.getCause();
}
if (exception instanceof UnauthorizedException)
{
return handleUnauthorizedException(clientMessage, exception);
}
if (exception instanceof AccessDeniedException)
{
return handleAccessDeniedException(clientMessage, exception);
}
return super.handleClientMessageProcessingError(clientMessage, ex);
}
...
private Message<byte[]> handleUnauthorizedException(Message<byte[]> clientMessage, Throwable ex)
{
ApiError apiError = new ApiError(ErrorCodeConstants.UNAUTHORIZED, ex.getMessage());
return prepareErrorMessage(clientMessage, apiError, ErrorCodeConstants.UNAUTHORIZED_STRING);
}
private Message<byte[]> prepareErrorMessage(Message<byte[]> clientMessage, ApiError apiError, String errorCode)
{
String message = transformApiErrorToJSONString(apiError);
StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR);
setReceiptIdForClient(clientMessage, accessor);
accessor.setMessage(errorCode);
accessor.setLeaveMutable(true);
return MessageBuilder.createMessage(message != null ? message.getBytes() : EMPTY_PAYLOAD, accessor.getMessageHeaders());
}
我正在尝试使用 Websockets 和 STOMP 1.2。我想在 CONNECT 框架上使用 JWT 对用户进行身份验证,如果授权无效,则显示 return 错误消息。这是通道拦截器
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{
// ... Removed for readability
@Override
public void configureClientInboundChannel(ChannelRegistration registration)
{
registration.interceptors(new ChannelInterceptor()
{
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel)
{
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand()))
{
String token = accessor.getFirstNativeHeader(authorizationHeader);
if (token != null)
{
Authentication authentication = tokenUtil.getAuthentication(resolveToken(token));
accessor.setUser(authentication);
}
else
{
throw new UnauthorizedException();
}
}
return message;
}
});
}
}
这样错误栈会有
Failed to send client message to application via MessageChannel in session a63cf95f-4a5b-ef34-b74b-3264ec6dd61f. Sending STOMP ERROR to client.
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is com.example.error.exception.UnauthorizedException: You are not authorized. Please log in.
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:146) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:122) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:284) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:324) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:113) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access[=12=]0(StandardWebSocketHandlerAdapter.java:42) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onMessage(StandardWebSocketHandlerAdapter.java:84) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onMessage(StandardWebSocketHandlerAdapter.java:81) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
Caused by: com.example.error.exception.UnauthorizedException: You are not authorized. Please log in.
at com.exampleconfig.WebSocketConfig.preSend(WebSocketConfig.java:74) ~[classes/:na]
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:178) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:132) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 28 common frames omitted
并且类似的错误将被发送到客户端连接将被关闭。 但是连接关闭后,其他堆栈跟踪被写为输出:
2018-11-11 01:06:48.077 DEBUG 7259 --- [nio-8080-exec-2] s.w.s.h.LoggingWebSocketHandlerDecorator : StandardWebSocketSession[id=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f, uri=/api/ws] closed with CloseStatus[code=1000, reason=null]
2018-11-11 01:06:48.077 DEBUG 7259 --- [nio-8080-exec-2] o.s.w.s.m.SubProtocolWebSocketHandler : Clearing session a63cf95f-4a5b-ef34-b74b-3264ec6dd61f
2018-11-11 01:06:48.084 DEBUG 7259 --- [nio-8080-exec-2] org.springframework.web.SimpLogging : Detected unsent DISCONNECT message. Processing anyway.
2018-11-11 01:06:48.085 DEBUG 7259 --- [nio-8080-exec-2] org.springframework.web.SimpLogging : Processing DISCONNECT session=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f
2018-11-11 01:06:48.086 DEBUG 7259 --- [tboundChannel-1] o.s.w.s.m.SubProtocolWebSocketHandler : No session for GenericMessage [payload=byte[0], headers={simpMessageType=DISCONNECT_ACK, simpDisconnectMessage=GenericMessage [payload=byte[0], headers={simpMessageType=DISCONNECT, stompCommand=DISCONNECT, simpSessionAttributes={org.springframework.messaging.simp.SimpAttributes.COMPLETED=true}, simpSessionId=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f}], simpSessionId=a63cf95f-4a5b-ef34-b74b-3264ec6dd61f}]
2018-11-11 01:06:48.087 WARN 7259 --- [nio-8080-exec-2] w.s.h.ExceptionWebSocketHandlerDecorator : Unhandled exception after connection closed for ExceptionWebSocketHandlerDecorator [delegate=LoggingWebSocketHandlerDecorator [delegate=SubProtocolWebSocketHandler[StompSubProtocolHandler[v10.stomp, v11.stomp, v12.stomp]]]]
org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:146) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:122) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.StompSubProtocolHandler.afterSessionEnded(StompSubProtocolHandler.java:611) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.clearSession(SubProtocolWebSocketHandler.java:516) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.afterConnectionClosed(SubProtocolWebSocketHandler.java:385) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.afterConnectionClosed(WebSocketHandlerDecorator.java:85) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.afterConnectionClosed(LoggingWebSocketHandlerDecorator.java:72) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.afterConnectionClosed(ExceptionWebSocketHandlerDecorator.java:78) ~[spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.onClose(StandardWebSocketHandlerAdapter.java:144) [spring-websocket-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.apache.tomcat.websocket.WsSession.fireEndpointOnClose(WsSession.java:535) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsSession.onClose(WsSession.java:513) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processDataControl(WsFrameBase.java:347) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148) [tomcat-embed-websocket-9.0.12.jar:9.0.12]
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.12.jar:9.0.12]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.12.jar:9.0.12]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
Caused by: org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.1.1.RELEASE.jar:5.1.1.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-5.1.1.RELEASE.jar:5.1.1.RELEASE]
at org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor.preSend(ChannelSecurityInterceptor.java:69) ~[spring-security-messaging-5.1.1.RELEASE.jar:5.1.1.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:178) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:132) ~[spring-messaging-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 26 common frames omitted
我不知道 AccessDeniedException
从哪里来,我该如何处理。
如果我不在 preSend
方法中抛出 UnauthorizedException
,客户端将被连接,当它尝试发送内容时,它将收到 AccessDeniedException
,因为所有端点都需要经过身份验证的用户,然后关闭连接并打印几次 AccessDeniedException
的堆栈跟踪。
我在这里做错了什么,处理这种用例的正确方法是什么?
作为 AccessDenied 异常的解决方案,我没有配置为允许 DISCONNECT 消息通过安全。这是我现在的配置:
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer
{
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages)
{
messages
.simpTypeMatchers(SimpMessageType.CONNECT,
SimpMessageType.DISCONNECT, SimpMessageType.OTHER).permitAll()
.anyMessage().authenticated();
}
}
此外,我覆盖了 StompSubProtocolErrorHandler
,因此我可以向我的客户发送有关错误的自定义消息。
示例如下:
@Override
public Message<byte[]> handleClientMessageProcessingError(Message<byte[]>clientMessage, Throwable ex)
{
Throwable exception = ex;
if (exception instanceof MessageDeliveryException)
{
exception = exception.getCause();
}
if (exception instanceof UnauthorizedException)
{
return handleUnauthorizedException(clientMessage, exception);
}
if (exception instanceof AccessDeniedException)
{
return handleAccessDeniedException(clientMessage, exception);
}
return super.handleClientMessageProcessingError(clientMessage, ex);
}
...
private Message<byte[]> handleUnauthorizedException(Message<byte[]> clientMessage, Throwable ex)
{
ApiError apiError = new ApiError(ErrorCodeConstants.UNAUTHORIZED, ex.getMessage());
return prepareErrorMessage(clientMessage, apiError, ErrorCodeConstants.UNAUTHORIZED_STRING);
}
private Message<byte[]> prepareErrorMessage(Message<byte[]> clientMessage, ApiError apiError, String errorCode)
{
String message = transformApiErrorToJSONString(apiError);
StompHeaderAccessor accessor = StompHeaderAccessor.create(StompCommand.ERROR);
setReceiptIdForClient(clientMessage, accessor);
accessor.setMessage(errorCode);
accessor.setLeaveMutable(true);
return MessageBuilder.createMessage(message != null ? message.getBytes() : EMPTY_PAYLOAD, accessor.getMessageHeaders());
}