STOMP (Simple Text Oriented Messaging Protocol) использует текстовые кадры (frames), где каждый кадр состоит из команды, набора заголовков и тела сообщения. Заголовки в STOMP выполняют ключевую роль: они передают метаданные соединения, включая параметры аутентификации и авторизации.
На уровне протокола авторизация чаще всего реализуется через
заголовки кадра CONNECT или STOMP, который
отправляется клиентом при установлении соединения с брокером
сообщений.
Структура кадра подключения:
CONNECT
login: user
passcode: password
accept-version:1.2
host: example
\0
В современных приложениях вместо логина и пароля всё чаще используется токен-based авторизация, где ключевой элемент переносится в пользовательские заголовки.
STOMP.js предоставляет возможность передавать произвольные заголовки
при подключении к брокеру. Это делается через объект headers, который
передаётся в метод connect.
Пример базового подключения:
import { Client } from '@stomp/stompjs';
const client = new Client({
brokerURL: 'ws://localhost:8080/ws',
connectHeaders: {
login: 'user',
passcode: 'password'
}
});
client.activate();
В этом случае STOMP.js формирует CONNECT frame с
указанными заголовками и отправляет его на сервер.
При использовании SockJS транспортный уровень не меняет семантику заголовков STOMP — они по-прежнему передаются внутри STOMP frame, а не HTTP-заголовками WebSocket handshake.
В современных архитектурах чаще применяется JWT или аналогичные токены доступа. Это связано с тем, что STOMP-соединение обычно интегрируется в уже существующую систему аутентификации REST API или OAuth2.
Типовой вариант передачи JWT:
const token = localStorage.getItem('access_token');
const client = new Client({
brokerURL: 'ws://localhost:8080/ws',
connectHeaders: {
Authorization: `Bearer ${token}`
}
});
client.activate();
Серверная сторона должна быть настроена на извлечение заголовка
Authorization из STOMP CONNECT frame и проверку токена до
установления сессии.
Частая ошибка заключается в попытке передать авторизационные данные через WebSocket HTTP handshake. В стандартной браузерной реализации WebSocket невозможно модифицировать HTTP headers напрямую.
STOMP решает эту проблему, перенося уровень авторизации выше — в STOMP frame.
Сравнение:
Таким образом, именно STOMP headers становятся основным механизмом авторизации.
STOMP допускает передачу любых пользовательских заголовков. Это позволяет реализовывать гибкие схемы безопасности.
Пример кастомной схемы:
const client = new Client({
brokerURL: 'ws://localhost:8080/ws',
connectHeaders: {
'X-Auth-Token': token,
'X-Client-Type': 'web',
'X-Device-Id': 'device-123'
}
});
Такие заголовки часто используются для:
Важно учитывать, что сервер должен явно обрабатывать эти заголовки, иначе они будут проигнорированы брокером.
В экосистеме Spring Security STOMP заголовки проходят через
ChannelInterceptor, который позволяет перехватывать
CONNECT frame до установления сессии.
Пример серверной обработки:
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String authHeader = accessor.getFirstNativeHeader("Authorization");
if (authHeader == null || !validate(authHeader)) {
throw new AccessDeniedException("Invalid token");
}
}
return message;
}
После успешной проверки пользователь может быть привязан к
WebSocket-сессии через Principal.
RabbitMQ использует STOMP plugin, который поддерживает базовую
авторизацию через login и passcode, но также
допускает кастомные механизмы через плагины.
Пример подключения:
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
connectHeaders: {
login: 'guest',
passcode: 'guest'
}
});
Однако в production-средах такой подход считается небезопасным, и обычно заменяется на:
STOMP.js автоматически выполняет reconnect при потере соединения,
однако важно понимать, что connectHeaders должны быть либо
статичными, либо обновляться при каждом подключении.
Пример динамического обновления токена:
client.beforeConnect = () => {
const newToken = refreshToken();
client.connectHeaders = {
Authorization: `Bearer ${newToken}`
};
};
Без такого обновления возможна ситуация, когда клиент переподключается с устаревшим токеном, что приводит к отказу в авторизации.
После успешного CONNECT брокер формирует сессию,
связанную с набором данных:
Заголовки CONNECT не используются повторно в
SUBSCRIBE или SEND, однако могут быть частично
дублированы в отдельных сообщениях для трассировки или авторизации на
уровне маршрута.
Пример заголовков при отправке сообщения:
client.publish({
destination: '/app/chat',
body: JSON.stringify({ text: 'hello' }),
headers: {
'X-Request-Id': 'abc-123'
}
});
Эти заголовки не участвуют в авторизации, если сервер явно не реализует такую логику.
Передача токенов через STOMP headers требует строгого соблюдения следующих принципов:
wss://Особое внимание требуется при использовании промежуточных прокси (NGINX, API Gateway), которые могут модифицировать или логировать STOMP frames при некорректной конфигурации.
При использовании OAuth2 токен обычно передаётся в STOMP CONNECT как Bearer token. Сервер извлекает его и валидирует через introspection endpoint или локальную JWT-подпись.
Типовой сценарий:
Пример клиента:
const client = new Client({
brokerURL: 'wss://api.example.com/ws',
connectHeaders: {
Authorization: `Bearer ${accessToken}`
}
});
Несмотря на гибкость STOMP headers, существует ряд ограничений:
Поэтому авторизация должна проектироваться с учётом того, что STOMP headers — это текстовый канал ограниченной структуры.
Если сервер отклоняет CONNECT frame, клиент получает
ERROR frame:
ERROR
message:Authentication failed
Token invalid or expired
\0
STOMP.js в этом случае вызывает обработчик
onStompError:
client.onStompEr ror = (frame) => {
console.error(frame.headers['message']);
console.error(frame.body);
};
После ошибки соединение считается не установленным, и дальнейшие подписки невозможны до повторного подключения с корректными заголовками.