Как отправить кадр ошибки Stomp из итерцептора канала в случае недействительных запросов на подписку (Spring WebSocket)JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Как отправить кадр ошибки Stomp из итерцептора канала в случае недействительных запросов на подписку (Spring WebSocket)

Сообщение Anonymous »

Я использую Spring WebSocket с STOMP в приложении Java и настроил брокер сообщений с ChannelInterceptor через configureClientInboundChannel.
Что я пытаюсь сделать
Я хочу проверить аутентификацию и авторизацию во время запроса SUBSCRIBE (например, пользователь не прошел аутентификацию или нет) разрешено подписаться на комнату).

Когда проверка не удалась, я хочу:
  • Отправить кадр STOMP ERROR обратно клиенту
  • Проинструктировать клиента закрыть соединение WebSocket
Что работает на данный момент
  • Логика аутентификации внутри ChannelInterceptor#preSend работает правильно
  • Я могу обнаружить недействительные или несанкционированные попытки подписки, опущенные в примере кода.
Что не работает
  • Я не могу надежно отправить кадр STOMP ERROR обратно клиенту из preSend
  • Возврат созданного вручную сообщения ERROR не достигает внешнего интерфейса, как ожидалось
  • Возврат null только блокирует сообщение, но не уведомляет клиент
Чего я ожидал
Когда подписка недействительна, клиент должен получить кадр STOMP ERROR с сообщением типа «Несанкционированный доступ» или «Неверная полезная нагрузка подписки», после чего клиент закрывает соединение.
Что на самом деле происходит
  • Клиент не получает кадр ошибки
  • В некоторых случаях соединение остается открытым
  • Попытки решить эту проблему путем подключения дополнительных компонентов приводят к ошибкам циклической зависимости
Подходы, которые я пробовал
  • Внедрение clientOutboundChannel
    • Насколько я понимаю, входящие каналы не могут напрямую отправлять кадры клиентам
    • Это приводило к проблемам с циклическими зависимостями компонентов
  • Использование SimpMessagingTemplate внутри перехватчик
    • Также приводили к проблемам циклической зависимости с WebSocketConfig и брокером сообщений
Соответствующий код
  • WebSocketConfig регистрирует конечные точки STOMP и настраивает перехватчик входящего канала
  • SocketChannelInterceptor выполняет аутентификацию при CONNECT и авторизацию при SUBSCRIBE
  • Когда авторизация не удалась, я пытаюсь создать и вернуть кадр STOMP ERROR с помощью StompHeaderAccessor
@EnableWebSocketMessageBroker()
@Configuration()
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

private final SocketChannelInterceptor socketChannelInterceptor;

@Autowired
public WebSocketConfig(JwtService jwtService, SocketChannelInterceptor socketChannelInterceptor, UserService userService) {
this.socketChannelInterceptor = socketChannelInterceptor;

}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*");

registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}

@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
WebSocketMessageBrokerConfigurer.super.configureClientOutboundChannel(registration);
}

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/chat", "/queue");
registry.setUserDestinationPrefix("/user");
registry.setApplicationDestinationPrefixes("/app");
}

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

@Component()
public class SocketChannelInterceptor implements ChannelInterceptor {

private final JwtService jwtService;
private final UserDetailsService userDetailsService;

@Autowired()
SocketChannelInterceptor( JwtService jwtService, UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
this.jwtService = jwtService;
}

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

StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

assert accessor != null;
if(StompCommand.CONNECT.equals(accessor.getCommand())) {
String authHeader = accessor.getFirstNativeHeader("Authorization");
assert authHeader != null;
if(authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
UserDetails user = userDetailsService.loadUserByUsername(jwtService.extractUsername(token));

if(jwtService.isTokenValid(token, user)){
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user,
null,
user.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
accessor.setUser(authToken);
}
}
}

if (StompCommand.SUBSCRIBE == accessor.getCommand()) {

String destination = accessor.getDestination();
StompHeaderAccessor errorAccessor = StompHeaderAccessor.create(StompCommand.ERROR);

if (destination == null || destination.isBlank()) {
errorAccessor.setLeaveMutable(true);
errorAccessor.setMessage("Destination is blank");
errorAccessor.setSessionId(accessor.getSessionId());
return MessageBuilder.createMessage("", errorAccessor.getMessageHeaders());
}
if(destination.startsWith("/chat/")) {

String roomId = destination.substring("/chat/".length());
if(roomId.isBlank()) {
errorAccessor.setLeaveMutable(true);
errorAccessor.setMessage("Invalid Subscription Payload");
errorAccessor.setSessionId(accessor.getSessionId());
return null;
};

UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) accessor.getUser();
if(token == null) {
errorAccessor.setLeaveMutable(true);
errorAccessor.setMessage("Unauthorized Access");
errorAccessor.setSessionId(accessor.getSessionId());
return MessageBuilder.createMessage(new Byte[0], errorAccessor.getMessageHeaders());
}
Users user = (Users) token.getPrincipal();
UUID userId = user.getUserId();
//check if user is in room
};
}

return message;
}
}


Подробнее здесь: https://stackoverflow.com/questions/798 ... nvalid-sub
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»