Подключение к защищённым STOMP-каналам начинается с выбора
транспорта, который обеспечивает шифрование на уровне канала связи. В
большинстве современных приложений используется WebSocket поверх TLS, то
есть схема wss://, где весь трафик между клиентом и
сервером проходит через зашифрованное соединение.
При работе с STOMP.js защищённый канал формируется не только на
уровне транспорта, но и на уровне протокола обмена сообщениями. STOMP
добавляет поверх WebSocket семантику команд (CONNECT,
SUBSCRIBE, SEND), что позволяет встроить
механизмы авторизации и контроля доступа непосредственно в момент
установления соединения.
Базовый принцип заключается в замене небезопасного ws://
на wss://. Это аналог HTTPS для WebSocket.
import { Client } from "@stomp/stompjs";
const client = new Client({
brokerURL: "wss://api.example.com/ws",
reconnectDelay: 5000,
});
Использование TLS гарантирует:
Важно учитывать, что защита канала не заменяет прикладную
авторизацию: даже при wss сервер обязан проверять права
доступа на уровне STOMP-команд.
Наиболее распространённый способ авторизации — передача токена в
заголовках STOMP CONNECT. В STOMP.js это реализуется через
поле connectHeaders.
const token = localStorage.getItem("access_token");
const client = new Client({
brokerURL: "wss://api.example.com/ws",
connectHeaders: {
Authorization: `Bearer ${token}`,
},
reconnectDelay: 5000,
});
На серверной стороне этот заголовок извлекается при обработке
CONNECT и используется для валидации пользователя до
завершения установления сессии.
Ключевой момент заключается в том, что токен передаётся один раз — в момент handshake STOMP, а не в каждом сообщении. Это снижает накладные расходы и уменьшает поверхность атаки.
Перед тем как STOMP-сессия вообще начнётся, происходит HTTP Upgrade-запрос. На этом этапе можно внедрить дополнительные механизмы защиты:
Пример конфигурации серверной проверки (логика абстрактна):
function verifyHandshake(request) {
const origin = request.headers.origin;
const token = request.url.searchParams.get("token");
if (!isAllowedOrigin(origin)) return false;
if (!verifyJWT(token)) return false;
return true;
}
Хотя STOMP ещё не активен на этом этапе, ошибки здесь полностью предотвращают установление канала.
После установления соединения критически важно ограничивать доступ не
только к подключению, но и к конкретным топикам. В STOMP это реализуется
через серверную проверку SUBSCRIBE.
Пример логики:
/user/queue/messages/admin/*Даже если клиент вручную попытается подписаться на запрещённый канал, сервер обязан отклонить подписку.
В защищённых системах часто используется модель user-destination, где сообщения адресуются конкретному пользователю:
client.subscribe("/user/queue/notifications", (message) => {
console.log(JSON.parse(message.body));
});
Сервер сопоставляет STOMP-сессию с идентичностью пользователя, полученной из токена. Это позволяет исключить утечку сообщений между пользователями даже при одинаковых подписках.
Защищённые каналы требуют контроля живости соединения. STOMP.js поддерживает heartbeat-механизм:
const client = new Client({
brokerURL: "wss://api.example.com/ws",
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
});
Heartbeat выполняет две функции:
На защищённых каналах это также снижает риск удержания «зависших» сессий, которые могут быть использованы повторно.
В долгоживущих соединениях токен может устаревать. В STOMP нет встроенного механизма refresh, поэтому используется прикладная стратегия:
connectHeadersfunction reconnectWithNewToken(newToken) {
client.deactivate();
client.connectHeaders = {
Authorization: `Bearer ${newToken}`,
};
client.activate();
}
В защищённых системах важно, чтобы сервер инвалидировал старые сессии при смене токена, иначе возможен доступ по устаревшему контексту.
При ограничениях сети WebSocket может быть недоступен, и используется SockJS как транспорт-эмуляция. При этом защита сохраняется, так как поверх всё равно используется тот же STOMP-уровень.
import SockJS from "sockjs-client";
const client = new Client({
webSocketFactory: () => new SockJS("https://api.example.com/ws"),
});
Здесь критично, что даже при HTTP-fallback канал должен оставаться защищённым через HTTPS, иначе токены могут быть перехвачены.
Защита канала не ограничивается соединением и подписками. Каждое
сообщение SEND также должно проходить проверку:
Особенно важно это в multi-tenant системах, где разные клиенты используют один брокер.
STOMP-сессия должна быть строго привязана к:
При любой несостыковке сессия немедленно разрывается. Это предотвращает:
В защищённых архитектурах WebSocket/STOMP почти всегда проходит через прокси (Nginx, Traefik, API Gateway). Важно корректно настроить:
Ошибка на уровне прокси часто приводит к «ложной защищённости», когда соединение формально устанавливается, но сообщения теряются или авторизация не проходит.
Если используется брокер (например, RabbitMQ или встроенный брокер Spring), он может дополнительно проверять права:
Это создаёт второй слой защиты поверх STOMP-логики приложения и снижает риск компрометации при ошибке в клиенте.