Авторизация spring WebSocket через Bearer в header
Необходимо: Реализовать авторизацию по Bearer, чтобы не авторизованные пользователи не моги получать сообщения.
На данный момент:
- Клиент передаёт мне Bearer, но я не могу его получить в beforeHandshake, при этом в postSend я его вижу.
- У меня проект на CUBA, пользователи авторизуются в REST через Bearer, хотелось бы используя его же авторизоваться в WebSocket, но не знаю как сделать это через "Authentication authResult = authenticationManager.authenticate(authentication);" выдаёт ошибку (возможно не правильно инициализировал)
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/user");
registry.setApplicationDestinationPrefixes("/app/");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:8000")
.addInterceptors(new HttpSessionHandshakeInterceptor() {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
super.beforeHandshake(request, response, wsHandler, attributes);
// Получаем заголовок Authorization из запроса
HttpHeaders httpHeaders = request.getHeaders();
String authorizationHeader = httpHeaders.getFirst(HttpHeaders.AUTHORIZATION);
// Enumeration<String> headers = request.getHeaders("Authorization");
// Проверяем наличие Bearer токена
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String token = authorizationHeader.substring(7); // Извлекаем токен без "Bearer "
// Логика проверки токена здесь
// Например Spring Security для аутентификации пользователя
return true;
} else {
// Если отсутствует токен, отклоняем запрос
return false;
}
}
})
.withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new MyChannelInterceptor());
}
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import java.security.Principal;
public class MyChannelInterceptor implements ChannelInterceptor {
@Override
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
String token = accessor.getFirstNativeHeader("Authorization");
}
import { Client } from '@stomp/stompjs'
import SockJS from 'sockjs-client/dist/sockjs'
import { notification } from 'ant-design-vue'
const openNotification = (message) => {
notification[message.status]({
message: message.type,
description: message.messageText,
placement: 'topRight',
})
}
let client
const createWebSocket = () => {
try {
return new SockJS(import.meta.env.VITE_APP_API_WS)
}
catch (error) {
console.error('Ошибка при создании WebSocket:', error)
}
}
const subscribeToRoom = async (userId) => {
try {
await client.subscribe(`/user/${userId}/terminalmessage`, (message) => {
openNotification(JSON.parse(message.body))
})
}
catch {
console.log('Не удалось подписаться на комнату', userId)
}
}
export function connect(userId, token) {
console.error('token:', token)
client = new Client({
webSocketFactory: createWebSocket,
connectHeaders: {
Authorization: `Bearer ${token}`,
},
beforeConnect: () => {
return new Promise((resolve) => {
resolve()
})
},
onConnect: () => {
subscribeToRoom(userId)
},
onStompError: (frame) => {
console.error('STOMP error:', frame)
},
})
client?.activate()
}
export function disconnect() {
if (client?.connected) {
client.deactivate()
console.log('Disconnected')
}
}