Nagłówki HTTP w interfejsie API klienta Websockets

200

Wygląda na to, że łatwo jest dodać niestandardowe nagłówki HTTP do klienta Websocket za pomocą dowolnego klienta nagłówka HTTP, który to obsługuje, ale nie mogę znaleźć sposobu, aby to zrobić za pomocą interfejsu API JSON.

Wydaje się jednak, że powinny być obsługiwane te nagłówki w specyfikacji .

Czy ktoś ma jakiś pomysł, jak to osiągnąć?

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

W szczególności muszę mieć możliwość wysłania nagłówka autoryzacji HTTP.

Julien Genestoux
źródło
14
Myślę, że dobrym rozwiązaniem jest zezwolenie WebSocket na połączenie bez autoryzacji, ale następnie zablokowanie i poczekanie na serwerze, aby otrzymać autoryzację od webSocket, który prześle informacje autoryzacyjne w swoim zdarzeniu onopen.
Motes
Sugestia @Motes wydaje się najlepiej pasować. Wywołanie autoryzacji z onOpen było bardzo łatwe, co pozwala zaakceptować / odrzucić gniazdo na podstawie odpowiedzi na autoryzację. Początkowo próbowałem wysłać token uwierzytelniania w nagłówku Sec-WebSocket-Protocol, ale wydaje mi się, że to włamanie.
BatteryAcid
@Motes Witaj, czy mógłbyś wyjaśnić część „blokuj i czekaj na serwerze”? masz na myśli coś takiego: nie przetwarzaj żadnych wiadomości, dopóki nie pojawi się komunikat „auth”?
Himal
@Himal, tak, projekt serwera nie może wysyłać danych ani akceptować żadnych innych danych niż autoryzacja na początku połączenia.
Motes,
@Motes Dziękujemy za odpowiedź. Byłem trochę zdezorientowany przez część blokującą, ponieważ o ile wiem, nie możesz zablokować początkowej connectprośby. Korzystam z kanałów Django na zapleczu i zaprojektowałem go tak, aby akceptował połączenie w connectprzypadku zdarzenia. następnie ustawia flagę „is_auth” w receivezdarzeniu (jeśli widzi prawidłowy komunikat autoryzacji ). jeśli flaga is_auth nie jest ustawiona i nie jest to wiadomość uwierzytelniająca, wówczas zamyka połączenie.
Himal

Odpowiedzi:

210

Zaktualizowano 2x

Krótka odpowiedź: Nie, można podać tylko pole ścieżki i protokołu.

Dłuższa odpowiedź:

W interfejsie API JavaScript WebSockets nie ma metody określania dodatkowych nagłówków do wysłania przez klienta / przeglądarkę. Ścieżka HTTP („GET / xyz”) i nagłówek protokołu („Sec-WebSocket-Protocol”) można określić w konstruktorze WebSocket.

Nagłówek Sec-WebSocket-Protocol (który czasem jest rozszerzony do użycia w uwierzytelnianiu specyficznym dla websocket) jest generowany z opcjonalnego drugiego argumentu do konstruktora WebSocket:

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

Powyższe skutkuje następującymi nagłówkami:

Sec-WebSocket-Protocol: protocol

i

Sec-WebSocket-Protocol: protocol1, protocol2

Częstym wzorcem uzyskiwania uwierzytelnienia / autoryzacji WebSocket jest wdrożenie systemu biletowego, w którym strona hostująca klienta WebSocket żąda biletu od serwera, a następnie przekazuje ten bilet podczas konfiguracji połączenia WebSocket albo w ciągu adresu URL / zapytania, w polu protokołu, lub wymagany jako pierwszy komunikat po nawiązaniu połączenia. Serwer pozwala wtedy na kontynuowanie połączenia tylko wtedy, gdy bilet jest ważny (istnieje, nie był już używany, adres IP klienta zakodowany w dopasowaniu biletu, znacznik czasu w bilecie jest aktualny itp.). Oto podsumowanie informacji o bezpieczeństwie WebSocket: https://devcenter.heroku.com/articles/websocket-security

Podstawowe uwierzytelnianie było wcześniej opcją, ale było przestarzałe, a nowoczesne przeglądarki nie wysyłają nagłówka, nawet jeśli jest określony.

Podstawowe informacje uwierzytelniające (wycofane) :

Nagłówek autoryzacji jest generowany z pola nazwy użytkownika i hasła (lub tylko nazwy użytkownika) identyfikatora URI WebSocket:

var ws = new WebSocket("ws://username:[email protected]")

Powyższe powoduje powstanie następującego nagłówka z ciągiem „nazwa użytkownika: hasło” zakodowanym base64:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Przetestowałem podstawowe uwierzytelnianie w Chrome 55 i Firefox 50 i zweryfikowałem, że podstawowe informacje o uwierzytelnianiu są rzeczywiście negocjowane z serwerem (może to nie działać w przeglądarce Safari).

Podziękowania dla Dmitry'ego Franka za podstawową odpowiedź na autoryzację

kanaka
źródło
37
Natknąłem się na ten sam problem. Szkoda, że ​​te standardy są tak słabo zintegrowane. Można się spodziewać, że sprawdzą interfejs API XHR w celu znalezienia wymagań (ponieważ WebSockets i XHR są powiązane) dla interfejsu API WebSockets, ale wygląda na to, że same opracowują API na wyspie.
eleotlecram
4
@eleotlecram, dołącz do grupy roboczej HyBi i zaproponuj ją. Grupa jest otwarta dla publiczności i trwają prace nad kolejnymi wersjami protokołu.
kanaka
5
@Charlie: jeśli w pełni kontrolujesz serwer, jest to jedna z opcji. Bardziej powszechnym podejściem jest generowanie biletu / tokena ze zwykłego serwera HTTP, a następnie zlecenie klientowi wysłania biletu / tokena (jako ciąg zapytania w ścieżce do pliku websocket lub jako pierwsza wiadomość z pliku websocket). Serwer WebSocket sprawdza następnie, czy bilet / token jest ważny (nie wygasł, nie był już używany, pochodzący z tego samego adresu IP co podczas tworzenia itp.). Ponadto uważam, że większość klientów websockets obsługuje podstawowe uwierzytelnianie (może ci to nie wystarczyć). Więcej informacji: devcenter.heroku.com/articles/websocket-security
kanaka
3
Myślę, że to z założenia. Mam wrażenie, że implementacja celowo pożycza z HTTP, ale zachowaj je możliwie jak najbardziej oddzielnie. Tekst w szczegółach jest kontynuowany: „Jednak projekt nie ogranicza WebSocket do HTTP, a przyszłe implementacje mogłyby korzystać z prostszego uzgadniania przez dedykowany port bez ponownego opracowywania całego protokołu. Ten ostatni punkt jest ważny, ponieważ wzorce ruchu w interaktywnych wiadomościach nie jest ściśle zgodny ze standardowym ruchem HTTP i może powodować nietypowe obciążenia niektórych komponentów ”.
3
Niestety nie działa to w Edge. Dzięki, MS: /
sibbl
40

Więcej alternatywnego rozwiązania, ale wszystkie nowoczesne przeglądarki wysyłają pliki cookie domeny wraz z połączeniem, więc używając:

var authToken = 'R3YKZFKBVi';

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

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

Zakończ z nagłówkami połączenia żądania:

Cookie: X-Authorization=R3YKZFKBVi
Tim
źródło
1
To wydaje się najlepszym sposobem na przekazywanie tokenów dostępu przy połączeniu. W tym momencie.
cammil
1
co jeśli identyfikator URI serwera WS różni się od identyfikatora URI klienta?
Duński
35

Problem nagłówka autoryzacji HTTP można rozwiązać w następujący sposób:

var ws = new WebSocket("ws://username:[email protected]/service");

Następnie odpowiedni nagłówek HTTP autoryzacji podstawowej zostanie ustawiony z podanym usernamei password. Jeśli potrzebujesz podstawowej autoryzacji, wszystko jest gotowe.


Chcę Bearerjednak użyć i skorzystałem z następującej sztuczki: Łączę się z serwerem w następujący sposób:

var ws = new WebSocket("ws://[email protected]/service");

A kiedy mój kod po stronie serwera otrzymuje nagłówek Podstawowej autoryzacji z niepustą nazwą użytkownika i pustym hasłem, wówczas interpretuje nazwę użytkownika jako token.

Dmitry Frank
źródło
12
Próbuję rozwiązania zaproponowanego przez ciebie. Ale nie widzę dodania nagłówka autoryzacji do mojego żądania. Próbowałem tego przy użyciu różnych przeglądarek, np. Chrome V56, Firefox V51.0. Pracuję na moim serwerze lokalnym. więc adres URL websocket to „ws: // nazwa_użytkownika: moje_hasło @ localhost: 8080 / mywebsocket”. Masz pojęcie, co może być nie tak? Dzięki
LearnToLive,
4
Czy przesyłanie tokena przez adres URL jest bezpieczne?
Mergasow
2
Pusta / ignorowana nazwa użytkownika i niepuste hasło, ponieważ token może być lepszy, ponieważ nazwy użytkowników mogą zostać zarejestrowane.
AndreKR
9
Zgadzam się z @LearnToLive - użyłem tego z wss (np. wss://user:[email protected]/ws) I nie otrzymałem Authorizationnagłówka po stronie serwera (używając Chrome w wersji 60)
user9645
6
Mam ten sam problem co @LearnToLive i @ user9645; ani chrome, ani Firefox nie dodają nagłówka autoryzacji, gdy identyfikator URI jest w wss://user:pass@hostformacie. Czy to nie jest obsługiwane przez przeglądarki, czy też coś idzie nie tak z uściskiem dłoni?
David Kaczyński
19

Nie możesz dodawać nagłówków, ale jeśli musisz tylko przekazać wartości do serwera w momencie połączenia, możesz określić część ciągu zapytania w adresie URL:

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

Ten adres URL jest prawidłowy, ale - oczywiście - musisz zmodyfikować kod serwera, aby go przeanalizować.

Gabriele Carioli
źródło
14
należy zachować ostrożność przy tym rozwiązaniu, ciąg zapytania może zostać przechwycony, zalogowany w serwerach proxy itp., więc przekazywanie poufnych informacji (użytkownicy / hasło / tokeny uwierzytelniające) w ten sposób nie będzie wystarczająco bezpieczne.
Nir
5
@Nir z WSS kwerenda powinna być prawdopodobnie bezpieczna
Sebastien Lorber
8
ws to zwykły tekst. Za pomocą protokołu ws wszystko można przechwycić.
Gabriele Carioli
1
@SebastienLorber użycie ciągu zapytania nie jest bezpieczne, nie jest szyfrowane, to samo dotyczy HTTPS, ale ponieważ używany jest protokół „ws: // ...”, to naprawdę nie ma znaczenia.
Lu4
5
@ Lu4 ciąg zapytania jest szyfrowany, ale istnieje wiele innych powodów, aby nie dodawać wrażliwych danych jako parametrów zapytania URL stackoverflow.com/questions/499591/are-https-urls-encrypted / ... & blog.httpwatch.com/2009 /
16

Nie możesz wysłać niestandardowego nagłówka, gdy chcesz nawiązać połączenie WebSockets za pomocą JavaScript WebSockets API. Możesz używać Subprotocolsnagłówków, używając drugiego konstruktora klasy WebSocket:

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

a następnie możesz pobrać nagłówki Subprotocols za pomocą Sec-WebSocket-Protocolklucza na serwerze.

Istnieje również ograniczenie, wartości nagłówków Subprotocols nie mogą zawierać przecinka ( ,)!

Saeed Zarinfam
źródło
1
Czy Jwt może zawierać przecinek?
CESCO
1
Nie wierzę w to. JWT składa się z trzech bloków danych zakodowanych w base64, z których każdy jest oddzielony kropką. Uważam, że wyklucza to przecinek.
BillyBBone
2
Wdrożyłem to i działa - po prostu czuję się dziwnie. dzięki
BatteryAcid
3
sugerujesz, że używamy Sec-WebSocket-Protocolnagłówka jako alternatywy dla Authorizationnagłówka?
rrw
13

Wysłanie nagłówka autoryzacji nie jest możliwe.

Dołączenie parametru zapytania do tokena jest opcją. Jednak w niektórych okolicznościach wysyłanie głównego tokena logowania w postaci zwykłego tekstu jako parametru zapytania może być niepożądane, ponieważ jest bardziej nieprzejrzysty niż używanie nagłówka i kończy się logowaniem gdziekolwiek. Jeśli rodzi to obawy związane z bezpieczeństwem, alternatywą jest użycie dodatkowego tokena JWT tylko dla gniazd sieciowych .

Utwórz punkt końcowy REST do generowania tego JWT , do którego oczywiście mogą uzyskać dostęp tylko użytkownicy uwierzytelnieni za pomocą głównego tokena logowania (przesyłanego przez nagłówek). Gniazdo sieciowe JWT można skonfigurować inaczej niż token logowania, np. Z krótszym czasem oczekiwania, więc bezpieczniej jest wysłać jako parametr zapytania żądania aktualizacji.

Utwórz osobny JwtAuthHandler dla tej samej trasy, na której zarejestrujesz SockJS eventbusHandler . Upewnij się, że twój moduł obsługi autoryzacji jest najpierw zarejestrowany, abyś mógł sprawdzić token gniazda sieciowego w swojej bazie danych (JWT powinien być w jakiś sposób powiązany z użytkownikiem w backendu).

Norbert Schöpke
źródło
To było jedyne bezpieczne rozwiązanie, jakie mogłem wymyślić dla Websockets API Gateway. Slack robi coś podobnego ze swoim API RTM i mają 30 sekundowy limit czasu.
andrhamm
2

Całkowicie zhakowałem to w ten sposób, dzięki odpowiedzi Kanaka.

Klient:

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

Serwer (w tym przykładzie używa Koa2, ale powinien być podobny wszędzie):

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...
Ryan Weiss
źródło
4
Czy to nie tylko przekazuje token w sekcji, w której klient powinien zażądać jednego lub kilku konkretnych protokołów? Mogę sprawić, że to zadziała, nie ma problemu, ale postanowiłem nie robić tego i raczej robić to, co sugeruje Motes, i blokować, dopóki token auth nie zostanie wysłany przez onOpen (). Przeładowanie nagłówka żądania protokołu wydaje mi się niewłaściwe, a ponieważ mój interfejs API jest przeznaczony do publicznego użytku, myślę, że będzie to nieco mylące dla konsumentów mojego interfejsu API.
Jay
0

Mój przypadek:

  • Chcę połączyć się z produkcyjnym serwerem WS www.mycompany.com/api/ws...
  • przy użyciu prawdziwych danych uwierzytelniających (ciasteczka sesji) ...
  • ze strony lokalnej ( localhost:8000).

Ustawienie document.cookie = "sessionid=foobar;path=/"nie pomoże, ponieważ domeny się nie zgadzają.

Rozwiązanie :

Dodaj 127.0.0.1 wsdev.company.comdo /etc/hosts.

W ten sposób Twoja przeglądarka będzie używać plików cookie mycompany.compodczas nawiązywania www.mycompany.com/api/wspołączenia z prawidłową subdomeną wsdev.company.com.

Max Malysh
źródło
-1

W mojej sytuacji (Azure Time Series Insights wss: //)

Za pomocą opakowania ReconnectingWebsocket udało się uzyskać dodanie nagłówków za pomocą prostego rozwiązania:

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

Gdzie ładowność w tym przypadku to:

{
  "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
źródło
-3

Technicznie rzecz biorąc, będziesz wysyłać te nagłówki przez funkcję łączenia przed fazą aktualizacji protokołu. To zadziałało dla mnie w nodejsprojekcie:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
Jerzy
źródło
3
Dotyczy to klienta websocket w npm (dla węzła). npmjs.com/package/websocket Ogólnie to byłoby dokładnie to, czego szukam, ale w przeglądarce.
arnuschky
1
Jest to odrzucone, ponieważ ten parametr nagłówków znajduje się w warstwie protokołu WebSocket, a pytanie dotyczy nagłówków HTTP.
Toilal
headerspowinien mieć wartość null lub obiekt określający dodatkowe dowolne nagłówki żądań HTTP do wysłania wraz z żądaniem”. z WebSocketClient.md ; dlatego headerstutaj jest warstwa HTTP.
momocow
Ponadto każdy, kto chce dostarczyć niestandardowe nagłówki, powinien pamiętać o sygnaturze funkcji connectmetody opisanej jako connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), tj. headersNależy ją podać requestOptionsna przykład wraz z ws.connect(url, '', headers, null). originW tym przypadku można zignorować tylko ciąg znaków.
momocow
-4

Możesz przekazać nagłówki jako klucz-wartość w trzecim parametrze (opcjach) wewnątrz obiektu. Przykład z tokenem autoryzacji. Pozostaw protokół (drugi parametr) na wartość NULL

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

Edycja: Wydaje się, że to podejście działa tylko z biblioteką nodejs, a nie ze standardową implementacją przeglądarki. Pozostawienie go, ponieważ może być przydatne dla niektórych osób.

Nodens
źródło
Miałem nadzieję przez chwilę. Wydaje się, że nie ma przeciążenia, biorąc trzeci parametr w WebSocket ctor.
Levitikon
Pomysł pochodzi z kodu wscat. github.com/websockets/wscat/blob/master/bin/wscat wiersz 261, który korzysta z pakietu ws. Myślałem, że to standardowe użycie.
Nodens