HTTP заголовки в клієнтському API Websockets


200

Схоже, легко додавати власні заголовки HTTP до свого клієнта веб-сокета за допомогою будь-якого клієнта заголовка HTTP, який підтримує це, але я не можу знайти, як це зробити за допомогою JSON API.

Однак, схоже, що ці заголовки повинні бути підтримані в специфікації .

У когось є поняття, як цього досягти?

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

Зокрема, мені потрібно мати можливість надіслати заголовок авторизації HTTP.


14
Я думаю, що хорошим рішенням є дозволити WebSocket підключитися без авторизації, але потім заблокувати та почекати на сервері отримати авторизацію від webSocket, яка передаватиме інформацію про авторизацію у своєму події onopen.
Мотес

Пропозиція від @Motes, здається, найкраще підходить. Зробити виклик авторизації від onOpen було дуже просто, що дозволяє прийняти / відхилити сокет на основі відповіді на авторизацію. Спочатку я намагався надсилати токен аутентифікатора в заголовок Sec-WebSocket-Protocol, але це виглядає як хак.
BatteryAcid

@Motes Привіт, ви могли б пояснити частину "блокувати і чекати на сервері"? ти маєш на увазі щось на зразок не обробляти жодне повідомлення, поки не з’явиться повідомлення "auth"?
Himal

@Himal, так серверний дизайн не повинен надсилати дані або приймати будь-які інші дані, ніж авторизація на початку з'єднання.
Мотес

@Motes Дякую за відповідь. Мене збентежила блокуюча частина, оскільки, наскільки я розумію, ви не можете заблокувати початковий connectзапит. Я використовую канали Джанго на зворотному кінці, і я створив його для того, щоб прийняти з'єднання під час connectподії. Потім він встановлює прапор "is_auth" у receiveвипадку події (якщо він бачить дійсне повідомлення про аутентифікацію). якщо прапор is_auth не встановлений і це не авторське повідомлення, він закриває з'єднання.
Himal

Відповіді:


210

Оновлено 2 рази

Коротка відповідь: Ні, можна вказати лише поле шляху та протоколу.

Більш довга відповідь:

В API WebSockets JavaScript немає методу для визначення додаткових заголовків для клієнта / браузера, що надсилається. Шлях HTTP ("GET / xyz") та заголовок протоколу ("Sec-WebSocket-протокол") можуть бути визначені в конструкторі WebSocket.

Заголовок протоколу Sec-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")

Вищезазначене призводить до наступного заголовка з кодуваним рядком "ім'я користувача: пароль" base64:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

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

Дякую Дмитру Франку за основну авторську відповідь


37
Я зіткнувся з тією ж проблемою. Шкода, що ці стандарти настільки погано інтегровані. Ви очікуєте, що вони переглянуть API XHR, щоб знайти вимоги (оскільки WebSockets і XHR пов'язані) для API WebSockets, але, здається, вони лише розробляють API на острові самостійно.
eleotlecram

4
@eleotlecram, приєднуйся до робочої групи HyBi та запропонуй її. Група відкрита для громадськості і триває робота над наступними версіями протоколу.
канака

5
@Charlie: якщо ви повністю керуєте сервером, це один варіант. Більш поширеним підходом є генерування квитка / маркера з вашого звичайного сервера HTTP, а потім клієнт надсилає квиток / маркер (або як рядок запиту в шляху веб-сокета, або як перше повідомлення веб-сокета). Потім сервер websocket підтверджує, що квиток / маркер дійсний (ще не закінчився, ще не використовувався, з того самого IP, що і під час створення тощо). Крім того, я вважаю, що більшість клієнтів веб-сокетів підтримують базовий auth (можливо, для вас це недостатньо). Більш детальна інформація: devcenter.heroku.com/articles/websocket-security
Канак

3
Я думаю, це за дизайном. Я маю враження, що реалізація навмисно запозичує HTTP, але тримайте їх максимально розділеними по дизайну. Текст у специфікації продовжується: "Однак дизайн не обмежує WebSocket HTTP, і майбутні реалізації можуть використовувати більш просте рукостискання через виділений порт, не винаходивши весь протокол. Цей останній момент важливий, тому що схеми трафіку інтерактивних повідомлень роблять не відповідає стандартному трафіку HTTP і може викликати незвичні навантаження на деякі компоненти ".

3
На жаль, це, здається, не працює в Edge. Дякую, мс: /
сіббл

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

1
що робити, якщо URI сервера WS відрізняється від URI клієнта?
Датська

35

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

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

Тоді буде встановлено належний HTTP-заголовок базового авторизації з наданими usernameта password. Якщо вам потрібна базова авторизація, тоді все налаштовано.


BearerОднак я хочу використовувати , і я вдався до такого фокусу: я підключаюсь до сервера наступним чином:

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

І коли мій код на стороні сервера отримує заголовок Basic Authorization з не порожнім ім'ям користувача та порожнім паролем, він інтерпретує ім'я користувача як маркер.


12
Я намагаюся запропонувати вам рішення. Але я не в змозі побачити заголовок авторизації, який додається до мого запиту. Я спробував це за допомогою різних браузерів, наприклад, Chrome V56, Firefox V51.0. Я запускаю свій сервер на своєму localhost. тож 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
Потрібно бути обережним з цим рішенням, рядок запиту може бути перехоплений, входити в проксі і т.д.
Нір

5
@Nir з WSS запит, напевно, повинен бути безпечним
Себастьян Лорбер

8
w - звичайний текст. За допомогою протоколу ws можна перехопити все, що завгодно.
Габріеле Каріолі

1
@SebastienLorber не безпечно використовувати рядок запиту, вона не шифрується, те саме стосується HTTPS, але оскільки використовується протокол "ws: // ...", це насправді не має значення.
Лу4,

5
@ LU4 рядок запиту шифрується, але є безліч інших причин , щоб не додати конфіденційні дані в URL запиту параметрів stackoverflow.com/questions/499591/are-https-urls-encrypted / ... & blog.httpwatch.com/2009 /

16

Не можна надсилати власні заголовки, коли ви хочете встановити з'єднання WebSockets за допомогою API WebSockets JavaScript. Ви можете використовувати Subprotocolsзаголовки, використовуючи конструктор другого класу WebSocket:

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

і тоді ви можете отримати заголовки Subprotocols за допомогою Sec-WebSocket-Protocol ключа на сервері.

Існує також обмеження, ваші значення заголовків Subprotocols не можуть містити коми ( ,)!


1
Чи може Jwt містити кому?
CESCO

1
Я не вірю в це. JWT складається з трьох базових 64 кодованих корисних навантажень, кожне розділене періодом. Я вважаю, що це виключає можливість кома.
BillyBBone

2
Я це реалізував, і це працює - просто дивно. дякую
BatteryAcid

3
Ви пропонуєте використовувати ми Sec-WebSocket-Protocolзаголовок як замінник Authorizationзаголовка?
rrw

13

Надіслати заголовок авторизації неможливо.

Додавання параметра запиту токена - це варіант. Однак, за деяких обставин, може бути небажаним надсилати основний маркер для входу в простому тексті як параметр запиту, оскільки він є більш непрозорим, ніж використання заголовка, і в кінцевому підсумку буде реєструватися хтось кудись. Якщо це викликає занепокоєння щодо безпеки у вас, альтернативою є використання вторинного маркера JWT тільки для матеріалів веб-розетки .

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

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


Це єдине безпечне рішення, яке я міг придумати для веб-розеток API Gateway. Slack робить щось подібне з їх RTM API, і вони мають 30 секунд таймаут.
andrhamm

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
Хіба це не просто передає ваш маркер у розділі, де ваш клієнт повинен запитувати один або кілька конкретних протоколів? Я можу це зробити так, що це теж не проблема, але я вирішив не робити цього і скоріше робити те, що запропонував Мотес, і заблокувати, поки маркер автентифікації не буде надіслано 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
}}}

-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

" headersповинен бути або нульовим, або об'єктом із зазначенням додаткових довільних заголовків запитів HTTP, що надсилаються разом із запитом." від WebSocketClient.md ; отже, headersтут є HTTP-рівень.
momocow

Крім того , кожен , хто хоче , щоб забезпечити призначені для користувача заголовки повинні мати на увазі функцію підпису connectметоду, описується як connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), тобто headersповинна бути забезпечена поряд з requestOptions, наприклад, ws.connect(url, '', headers, null). originУ цьому випадку можна ігнорувати лише рядок.
momocow

-4

Ви можете передавати заголовки як значення "ключ" у третьому параметрі (опціях) всередині об'єкта. Приклад із маркером авторизації. Залишив протокол (другий параметр) як нульовий

ws = новий WebSocket ('ws: // localhost', null, {заголовки: {Авторизація: маркер}})

Редагувати: Здається, що цей підхід працює лише з бібліотекою nodejs, а не зі стандартною реалізацією браузера. Залишаючи це, тому що воно може бути корисним для деяких людей.


Я сподівався на секунду. Здається, не буде перевантаження з третім парам в ctor WebSocket.
Левитикон

Отримав ідею з коду wscat. Лінія 261 github.com/websockets/wscat/blob/master/bin/wscat 26, яка використовує пакет ws. Думав, що це стандартне використання.
Ноден
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.