Передача учетных данных

STOMP-протокол определяет механизм установления соединения через специальный управляющий кадр CONNECT, в котором и передаются данные аутентификации. В контексте STOMP.js именно этот этап становится ключевой точкой интеграции с системой авторизации, поскольку WebSocket сам по себе не предоставляет унифицированного механизма передачи учетных данных после установки соединения.

CONNECT-кадр формируется клиентом и отправляется брокеру сразу после установления транспортного канала (WebSocket или SockJS). Внутри этого кадра находятся заголовки, которые и используются для передачи идентификационной информации:

  • login
  • passcode
  • Authorization (или кастомные заголовки)
  • любые дополнительные метаданные, поддерживаемые брокером

Эти заголовки не являются HTTP-заголовками в классическом смысле. Они существуют исключительно в рамках STOMP-сессии и интерпретируются серверной частью брокера.

Браузерный WebSocket API не позволяет динамически управлять HTTP-заголовками после выполнения handshake-запроса. Это означает, что стандартные механизмы вроде Authorization header в HTTP-запросе недоступны на этапе установки WebSocket-соединения.

STOMP.js решает эту проблему переносом аутентификации на уровень протокольного сообщения CONNECT. В результате:

  • транспортное соединение открывается без учетных данных
  • идентификация выполняется внутри STOMP-сессии
  • сервер валидирует CONNECT-кадр и либо принимает соединение, либо разрывает его

Базовая передача учетных данных через STOMP.js

В STOMP.js учетные данные обычно передаются через параметры подключения клиента:

import { Client } from '@stomp/stompjs';

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  connectHeaders: {
    login: 'user1',
    passcode: 'password123'
  },
  onConnect: () => {
    console.log('Connected');
  }
});

client.activate();

В этом случае STOMP.js автоматически формирует CONNECT-кадр следующего вида:

CONNECT
login:user1
passcode:password123

\0

Брокер, например RabbitMQ или Spring STOMP endpoint, обрабатывает эти заголовки в момент аутентификации сессии.

Использование токенов вместо логина и пароля

В современных архитектурах прямое использование пар логин/пароль встречается реже. Более распространена схема с токенами, чаще всего JWT. В этом случае учетные данные передаются через пользовательский заголовок:

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  connectHeaders: {
    Authorization: `Bearer ${token}`
  }
});

Такой подход позволяет:

  • отделить транспортный уровень от бизнес-аутентификации
  • использовать единый механизм авторизации для HTTP и WebSocket
  • упростить ротацию учетных данных

На серверной стороне токен извлекается из CONNECT-кадра и проходит проверку до завершения установки STOMP-сессии.

Передача учетных данных в SockJS-сценариях

При использовании SockJS появляется дополнительный уровень абстракции. Фактическое WebSocket-соединение может заменяться XHR-streaming или long polling, но STOMP-логика остается неизменной.

import SockJS from 'sockjs-client';
import { Client } from '@stomp/stompjs';

const client = new Client({
  webSocketFactory: () => new SockJS('http://localhost:8080/ws'),
  connectHeaders: {
    Authorization: `Bearer ${token}`
  }
});

В этом случае учетные данные все так же передаются внутри STOMP CONNECT, а не в HTTP handshake, что сохраняет единообразие модели безопасности.

Серверная обработка учетных данных

На стороне Spring-based брокеров обработка CONNECT-кадра может быть интегрирована в SecurityContext через перехватчик канала:

@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 (StompCommand.CONNECT.equals(accessor.getCommand())) {
                String auth = accessor.getFirstNativeHeader("Authorization");
                // проверка токена
            }
            return message;
        }
    });
}

На этом этапе соединение еще не считается установленным, что позволяет отклонить клиента до подписки на топики и получения сообщений.

Разделение аутентификации и авторизации

Передача учетных данных через CONNECT-кадр решает только задачу аутентификации. Авторизация выполняется на уровне команд SUBSCRIBE и SEND.

Типичный сценарий:

  1. CONNECT — идентификация пользователя
  2. SUBSCRIBE — проверка прав доступа к destination
  3. SEND — проверка права публикации сообщений

Таким образом, учетные данные используются только на первом этапе, а дальнейшее поведение определяется контекстом сессии.

Повторное подключение и сохранение учетных данных

STOMP.js поддерживает автоматическое переподключение, при котором CONNECT-кадр формируется повторно. Это означает, что все учетные данные должны быть доступны в момент реконнекта:

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  reconnectDelay: 5000,
  connectHeaders: {
    Authorization: `Bearer ${token}`
  }
});

При потере соединения библиотека заново отправляет CONNECT с теми же заголовками. Это накладывает требования на:

  • актуальность токена
  • возможность его обновления до reconnection
  • обработку ошибок AUTH_EXPIRED или аналогичных STOMP ERROR кадров

Динамическое обновление учетных данных

В сценариях с истекающими токенами требуется обновление connectHeaders до переподключения:

client.beforeConnect = async () => {
  const newToken = await refreshToken();
  client.connectHeaders.Authorization = `Bearer ${newToken}`;
};

В некоторых реализациях используется внешнее управление состоянием, при котором STOMP-клиент не хранит токен, а получает его из функции-источника перед каждым CONNECT.

Обработка ошибок аутентификации

Если сервер отклоняет учетные данные, он отправляет STOMP ERROR кадр и закрывает соединение. STOMP.js обрабатывает это через callback:

const client = new Client({
  onStompError: (frame) => {
    console.error('Auth error:', frame.headers['message']);
  }
});

Типичные причины отказа:

  • неверный login/passcode
  • просроченный JWT
  • отсутствие прав на подключение к endpoint
  • блокировка пользователя на уровне брокера

Безопасность передачи учетных данных

Передача учетных данных через STOMP требует учета особенностей транспортного уровня:

  • WebSocket должен использовать wss:// для защиты данных
  • токены не должны логироваться на клиенте
  • connectHeaders не должны содержать чувствительные данные сверх необходимости
  • брокер должен валидировать CONNECT до назначения sessionId

Особенно критично исключить передачу паролей в открытом виде в production-среде. В современных системах предпочтение отдается токенам с ограниченным временем жизни и возможностью отзыва.

Согласование STOMP и внешних систем авторизации

В архитектурах с отдельным identity provider STOMP CONNECT часто становится точкой интеграции:

  • OAuth2 access token передается в CONNECT
  • сервер валидирует его через introspection endpoint
  • пользовательский контекст привязывается к STOMP session

Это позволяет унифицировать безопасность между REST API и WebSocket-каналом без дублирования логики аутентификации.