Заголовки HTTP в клиентском API Websockets

201

Похоже, что легко добавить пользовательские заголовки HTTP в ваш клиент websocket с любым клиентом заголовка HTTP, который поддерживает это, но я не могу найти, как это сделать с помощью JSON API.

Тем не менее, кажется, что эти заголовки должны быть поддержаны в спецификации .

Кто-нибудь знает, как этого добиться?

var ws = new WebSocket("ws://example.com/service");

В частности, мне нужно иметь возможность отправлять заголовок авторизации HTTP.

Жюльен Дженесту
источник
14
Я думаю, что хорошее решение - разрешить соединению WebSocket без авторизации, но затем заблокировать и подождать на сервере, чтобы получить авторизацию от webSocket, который будет передавать информацию авторизации в своем событии onopen.
Motes
Предложение @Motes кажется наиболее подходящим. Было очень легко сделать вызов авторизации из onOpen, который позволяет вам принять / отклонить сокет на основе ответа авторизации. Первоначально я пытался отправить токен аутентификации в заголовке Sec-WebSocket-Protocol, но это похоже на взлом.
BatteryAcid
@Motes Привет, не могли бы вы объяснить, что такое «блокировать и ждать на сервере»? Вы имеете в виду что-то вроде не обрабатывать сообщения, пока не появится сообщение "auth"?
Himal
@Himal, да, дизайн сервера не должен отправлять данные или принимать какие-либо другие данные, кроме авторизации в начале соединения.
Motes
@Motes Спасибо за ответ. Меня немного смутила блокирующая часть, поскольку, насколько я понимаю, вы не можете заблокировать первоначальный connectзапрос. Я использую каналы Django на заднем конце, и я разработал его, чтобы принимать соединение по connectсобытию. затем он устанавливает флаг «is_auth» в receiveсобытии (если он видит действительное сообщение аутентификации). если флаг is_auth не установлен и это не сообщение аутентификации, то соединение закрывается.
Himal

Ответы:

212

Обновлено 2x

Краткий ответ: Нет, можно указать только путь и поле протокола.

Более длинный ответ:

В API WebSockets JavaScript нет метода для указания дополнительных заголовков для клиента / браузера для отправки. Путь HTTP («GET / xyz») и заголовок протокола («Sec-WebSocket-Protocol») можно указать в конструкторе WebSocket.

Заголовок Sec-WebSocket-Protocol (который иногда расширяется для использования в специфичной для websocket аутентификации) генерируется из необязательного второго аргумента в конструкторе WebSocket:

var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);

Выше приведены результаты в следующих заголовках:

Sec-WebSocket-Protocol: protocol

и

Sec-WebSocket-Protocol: protocol1, protocol2

Обычным способом достижения аутентификации / авторизации WebSocket является внедрение системы создания билетов, в которой страница, на которой размещается клиент WebSocket, запрашивает билет с сервера, а затем передает этот билет во время установки соединения WebSocket либо в строке URL / запроса, либо в поле протокола, или требуется в качестве первого сообщения после установления соединения. Затем сервер разрешает продолжение соединения только в том случае, если заявка действительна (существует, еще не использовалась, IP-адрес клиента закодирован в совпадениях с заявкой, отметка времени в заявке недавно и т. Д.). Вот краткая информация о безопасности WebSocket: https://devcenter.heroku.com/articles/websocket-security

Раньше базовая аутентификация была опцией, но она устарела, и современные браузеры не отправляют заголовок, даже если он указан.

Основная информация об аутентификации (устарела) :

Заголовок авторизации создается из поля имени пользователя и пароля (или просто имени пользователя) URI WebSocket:

var ws = new WebSocket("ws://username:password@example.com")

Вышеприведенный результат приводит к следующему заголовку со строкой «username: password» в кодировке base64:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Я протестировал базовую аутентификацию в Chrome 55 и Firefox 50 и убедился, что базовая аутентификационная информация действительно согласовывается с сервером (это может не работать в Safari).

Спасибо Дмитрию Фрэнку за базовый ответ

канак
источник
38
Я сталкивался с той же проблемой. Жаль, что эти стандарты так плохо интегрированы. Можно ожидать, что они смотрят на API XHR, чтобы найти требования (поскольку WebSockets и XHR связаны) для API WebSockets, но, похоже, они просто разрабатывают API на острове самостоятельно.
eleotlecram
4
@eleotlecram, присоединитесь к рабочей группе HyBi и предложите ее. Группа открыта для общественности, и ведется работа над последующими версиями протокола.
Канака
5
@Charlie: если вы полностью контролируете сервер, это один из вариантов. Более распространенный подход - это сгенерировать тикет / токен с вашего обычного HTTP-сервера, а затем заставить клиента отправить тикет / токен (либо в виде строки запроса в пути веб-сокета, либо в качестве первого сообщения веб-сокета). Затем сервер веб-сокета проверяет, что билет / токен действителен (не истек, не использовался, поступает с того же IP-адреса, что и при создании, и т. Д.). Кроме того, я полагаю, что большинство клиентов websockets поддерживают базовую аутентификацию (хотя вам может быть недостаточно). Дополнительная информация: devcenter.heroku.com/articles/websocket-security
канака,
3
Я думаю, это по замыслу. У меня сложилось впечатление, что реализация намеренно заимствует из HTTP, но держу их как можно более отделенными от дизайна. Текст в спецификации продолжает: «Однако дизайн не ограничивает WebSocket HTTP, и в будущих реализациях может использоваться более простое рукопожатие через выделенный порт без повторного изобретения всего протокола. Этот последний момент важен, поскольку шаблоны трафика интерактивных сообщений делают не соответствует стандартному HTTP-трафику и может вызывать необычные нагрузки на некоторые компоненты. "
3
К сожалению, это не работает в Edge. Спасибо, MS: /
Сиббл
40

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

var authToken = 'R3YKZFKBVi';

document.cookie = 'X-Authorization=' + authToken + '; path=/';

var ws = new WebSocket(
    'wss://localhost:9000/wss/'
);

Получите заголовки соединения запроса:

Cookie: X-Authorization=R3YKZFKBVi
Тим
источник
1
Это кажется наилучшим способом передачи токенов доступа при соединении. В данный момент.
cammil
2
Что делать, если URI сервера WS отличается от URI клиента?
датский
@Danish Ну, тогда это не сработает, так как вы не можете устанавливать файлы cookie для клиентской части других доменов
Tofandel
35

Проблема с заголовком HTTP-авторизации может быть решена с помощью следующего:

var ws = new WebSocket("ws://username:password@example.com/service");

Затем будет установлен правильный HTTP-заголовок Basic Authorization с указанием usernameи password. Если вам нужна базовая авторизация, то все готово.


Я хочу использовать, Bearerоднако, и я прибег к следующей уловке: я подключаюсь к серверу следующим образом:

var ws = new WebSocket("ws://my_token@example.com/service");

И когда мой код на стороне сервера получает заголовок Basic Authorization с непустым именем пользователя и пустым паролем, он интерпретирует имя пользователя как токен.

Дмитрий Франк
источник
12
Я пробую решение, предложенное вами. Но я не вижу заголовка авторизации, добавляемого к моему запросу. Я пробовал использовать разные браузеры, например, Chrome V56, Firefox V51.0. Я запускаю свой сервер на локальном хосте. поэтому URL-адрес веб-сокета - «ws: // myusername: mypassword @ localhost: 8080 / mywebsocket». Есть идеи, что может быть не так? Спасибо
LearnToLive
4
Безопасно ли передавать токен через URL?
Мергасов
2
Пустое / игнорируемое имя пользователя и непустой пароль в качестве токена могут быть лучше, потому что имена пользователей могут быть зарегистрированы.
AndreKR
9
Я согласен с @LearnToLive - я использовал это с wss (например wss://user:password@myhost.com/ws) и не получил Authorizationзаголовок на стороне сервера (используя Chrome версии 60)
user9645
6
У меня та же проблема, что и у @LearnToLive и @ user9645; Ни Chrome, ни Firefox не добавляют заголовок авторизации, когда URI находится в wss://user:pass@hostформате. Это не поддерживается браузерами или что-то не так с рукопожатием?
Дэвид Качиньский
19

Вы не можете добавлять заголовки, но, если вам просто нужно передать значения на сервер в момент соединения, вы можете указать часть строки запроса в URL:

var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");

Этот URL-адрес действителен, но, разумеется, вам потребуется изменить код сервера для его анализа.

Габриэле Кариоли
источник
14
нужно быть осторожным с этим решением, строка запроса может быть перехвачена, авторизована в прокси и т. д., поэтому передача конфиденциальной информации (пользователей / пароля / токенов аутентификации) таким образом не будет достаточно безопасной.
Nir
5
@Nir с WSS, строка запроса, вероятно, должна быть безопасной
Себастьен Лорбер
8
WS простой текст. Используя протокол ws, все может быть перехвачено.
Габриэле
1
@SebastienLorber небезопасно использовать строку запроса, она не шифруется, это же относится и к HTTPS, но поскольку используется протокол "ws: // ...", это действительно не имеет значения.
Lu4
5
@ Lu4 строка запроса зашифрована, но есть множество других причин не добавлять конфиденциальные данные в качестве параметров запроса URL-адреса stackoverflow.com/questions/499591/are-https-urls-encrypted/… & blog.httpwatch.com/2009 /
16

Вы не можете отправлять пользовательский заголовок, если хотите установить соединение WebSockets с помощью JavaScript WebSockets API. Вы можете использовать Subprotocolsзаголовки, используя второй конструктор класса WebSocket:

var ws = new WebSocket("ws://example.com/service", "soap");

и затем вы можете получить заголовки Subprotocols, используя Sec-WebSocket-Protocolключ на сервере.

Существует также ограничение: значения заголовков ваших подпротоколов не могут содержать запятую ( ,)!

Саид Заринфам
источник
1
Может ли Jwt содержать запятую?
CESCO,
1
Я не верю в это. JWT состоит из трех полезных нагрузок в кодировке base64, каждая из которых разделена точкой. Я считаю, что это исключает возможность запятой.
BillyBBone
2
Я реализовал это, и это работает - просто чувствует себя странно. спасибо
BatteryAcid
3
Вы предлагаете использовать Sec-WebSocket-Protocol заголовок как альтернативу Authorizationзаголовку?
rrw
13

Отправка заголовка авторизации невозможна.

Присоединение параметра запроса токена является опцией. Однако в некоторых случаях может быть нежелательно отправлять основной токен входа в виде обычного текста в качестве параметра запроса, поскольку он более непрозрачен, чем использование заголовка, и в конечном итоге будет зарегистрирован, кто знает где. Если это вызывает у вас проблемы с безопасностью, альтернативой является использование вторичного токена JWT только для веб-сокетов .

Создайте конечную точку REST для генерации этого JWT , к которому, конечно, могут получить доступ только пользователи, аутентифицированные с помощью вашего основного токена входа (переданного через заголовок). Веб-сокет JWT может быть настроен иначе, чем ваш токен входа, например, с более коротким тайм-аутом, поэтому безопаснее отправлять его в качестве параметра запроса на обновление.

Создайте отдельный JwtAuthHandler для того же маршрута, на котором вы регистрируете SockJS eventbusHandler . Сначала убедитесь, что ваш обработчик аутентификации зарегистрирован, чтобы вы могли проверить токен веб-сокета в своей базе данных (JWT должен быть каким-то образом связан с вашим пользователем в бэкэнде).

Норберт Шёпке
источник
Это было единственное безопасное решение для веб-сокетов API Gateway. Slack делает нечто подобное со своим RTM API, и у них есть 30-секундный тайм-аут.
Андрэмм
2

Полностью взломал это, как это, благодаря ответу Канаки.

Клиент:

var ws = new WebSocket(
    'ws://localhost:8080/connect/' + this.state.room.id, 
    store('token') || cookie('token') 
);

Сервер (использующий Koa2 в этом примере, но должен быть похожим везде):

var url = ctx.websocket.upgradeReq.url; // can use to get url/query params
var authToken = ctx.websocket.upgradeReq.headers['sec-websocket-protocol'];
// Can then decode the auth token and do any session/user stuff...
Райан Вайс
источник
4
Разве это не передает ваш токен в раздел, где ваш клиент должен запрашивать один или несколько конкретных протоколов? Я могу заставить это работать, тоже не проблема, но я решил не делать этого, а делать то, что предложил Motes, и блокировать до тех пор, пока токен auth не будет отправлен на onOpen (). Перегрузка заголовка запроса протокола кажется мне неправильной, и, поскольку мой API предназначен для публичного использования, я думаю, что это будет немного запутывать потребителей моего API.
Джей
0

Мое дело:

  • Я хочу подключиться к производственному серверу WS www.mycompany.com/api/ws...
  • используя реальные учетные данные (сессионный cookie) ...
  • с локальной страницы ( localhost:8000).

Настройка document.cookie = "sessionid=foobar;path=/"не поможет, так как домены не совпадают.

Решение :

Добавить 127.0.0.1 wsdev.company.comв /etc/hosts.

Таким образом, ваш браузер будет использовать файлы cookie mycompany.comпри подключении к тому, www.mycompany.com/api/wsкак вы подключаетесь с действительного субдомена wsdev.company.com.

Макс Малыш
источник
-1

В моей ситуации (обзор временных рядов Azure wss: //)

Используя оболочку ReconnectingWebsocket и смог добиться добавления заголовков с помощью простого решения:

socket.onopen = function(e) {
    socket.send(payload);
};

Где полезная нагрузка в этом случае:

{
  "headers": {
    "Authorization": "Bearer TOKEN",
    "x-ms-client-request-id": "CLIENT_ID"
}, 
"content": {
  "searchSpan": {
    "from": "UTCDATETIME",
    "to": "UTCDATETIME"
  },
"top": {
  "sort": [
    {
      "input": {"builtInProperty": "$ts"},
      "order": "Asc"
    }], 
"count": 1000
}}}
Poopy McFartnoise
источник
-3

Технически, вы будете отправлять эти заголовки через функцию соединения до фазы обновления протокола. Это сработало для меня в nodejsпроекте:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
Джордж
источник
3
Это для клиента websocket в npm (для узла). npmjs.com/package/websocket В целом это будет именно то, что я ищу, но в браузере.
arnuschky
1
Он отклонен, потому что этот параметр заголовков находится на уровне протокола WebSocket, и вопрос касается заголовков HTTP.
Toilal
headersmsgstr " должен быть либо пустым, либо объектом, указывающим дополнительные произвольные заголовки HTTP-запроса для отправки вместе с запросом." от WebSocketClient.md ; следовательно, headersздесь есть уровень HTTP.
momocow
Кроме того, любой, кто хочет предоставить пользовательские заголовки, должен иметь в виду сигнатуру функции connectметода, описанную как connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), то есть headersдолжна быть предоставлена requestOptions, например, вместе с ws.connect(url, '', headers, null). В originэтом случае можно игнорировать только строку.
momocow
-4

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

ws = new WebSocket ('ws: // localhost', null, {headers: {Authorization: token}})

Редактировать: похоже, что этот подход работает только с библиотекой nodejs, а не со стандартной реализацией браузера. Оставляя это, потому что это может быть полезно для некоторых людей.

Ноденс
источник
Надеюсь на секунду. Похоже, что в WebSocket ctor нет перегрузки с принятием третьего параметра.
Левитикон
Получил идею из кода WSCAT. github.com/websockets/wscat/blob/master/bin/wscat строка 261, которая использует пакет ws. Думал, что это стандартное использование.
Nodens