HTTP-Header in der Websockets-Client-API

200

Es sieht so aus, als ob es einfach ist, Ihrem Websocket-Client mit jedem HTTP-Header-Client, der dies unterstützt, benutzerdefinierte HTTP-Header hinzuzufügen, aber ich kann nicht finden, wie dies mit der JSON-API gemacht wird.

Es scheint jedoch, dass diese Header in der Spezifikation unterstützt werden sollten .

Hat jemand eine Ahnung, wie man es erreicht?

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

Insbesondere muss ich in der Lage sein, einen HTTP-Autorisierungsheader zu senden.

Julien Genestoux
quelle
14
Ich denke, eine gute Lösung besteht darin, dem WebSocket zu erlauben, eine Verbindung ohne Autorisierung herzustellen, aber dann zu blockieren und auf dem Server zu warten, um die Autorisierung vom WebSocket zu erhalten, der Autorisierungsinformationen in seinem Onopen-Ereignis überträgt.
Motes
Der Vorschlag von @Motes scheint am besten zu passen. Es war sehr einfach, einen Autorisierungsaufruf von onOpen aus zu tätigen, mit dem Sie den Socket basierend auf der Autorisierungsantwort akzeptieren / ablehnen können. Ich habe ursprünglich versucht, ein Authentifizierungstoken im Sec-WebSocket-Protocol-Header zu senden, aber das fühlt sich wie ein Hack an.
BatteryAcid
@Motes Hallo, kannst du den Teil "Blockieren und auf dem Server warten" erklären? Du meinst so etwas wie keine Nachrichten verarbeiten, bis es eine "Auth" -Nachricht gibt?
Himal
@Himal, ja, das Serverdesign darf zu Beginn der Verbindung keine Daten senden oder andere Daten als die Autorisierung akzeptieren.
Motes
@Motes Danke für die Antwort. Der blockierende Teil hat mich etwas verwirrt, da Sie meines Wissens die ursprüngliche connectAnfrage nicht blockieren können . Ich verwende Django-Kanäle im Back-End und habe sie so konzipiert, dass sie die Verbindung bei einem connectEreignis akzeptieren . Anschließend wird im receiveEreignis ein "is_auth" -Flag gesetzt (wenn eine gültige Authentifizierungsnachricht angezeigt wird ). Wenn das Flag is_auth nicht gesetzt ist und es sich nicht um eine Authentifizierungsnachricht handelt, wird die Verbindung geschlossen.
Himal

Antworten:

210

2x aktualisiert

Kurze Antwort: Nein, nur das Pfad- und Protokollfeld kann angegeben werden.

Längere Antwort:

In der JavaScript- WebSockets-API gibt es keine Methode zum Angeben zusätzlicher Header, die der Client / Browser senden soll. Der HTTP-Pfad ("GET / xyz") und der Protokollheader ("Sec-WebSocket-Protocol") können im WebSocket-Konstruktor angegeben werden.

Der Sec-WebSocket-Protocol-Header (der manchmal erweitert wird, um für die Websocket-spezifische Authentifizierung verwendet zu werden) wird aus dem optionalen zweiten Argument für den WebSocket-Konstruktor generiert:

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

Das Obige führt zu folgenden Überschriften:

Sec-WebSocket-Protocol: protocol

und

Sec-WebSocket-Protocol: protocol1, protocol2

Ein gängiges Muster zum Erreichen der WebSocket-Authentifizierung / -Autorisierung besteht darin, ein Ticketing-System zu implementieren, bei dem die Seite, auf der sich der WebSocket-Client befindet, ein Ticket vom Server anfordert und dieses Ticket dann während des Aufbaus der WebSocket-Verbindung entweder in der URL / Abfragezeichenfolge im Protokollfeld weiterleitet. oder als erste Nachricht nach dem Herstellen der Verbindung erforderlich. Der Server lässt dann nur dann zu, dass die Verbindung fortgesetzt wird, wenn das Ticket gültig ist (vorhanden, noch nicht verwendet, Client-IP in Ticket-Übereinstimmungen codiert, Zeitstempel im Ticket ist aktuell usw.). Hier ist eine Zusammenfassung der WebSocket-Sicherheitsinformationen: https://devcenter.heroku.com/articles/websocket-security

Früher war die Standardauthentifizierung eine Option, diese ist jedoch veraltet, und moderne Browser senden den Header nicht, selbst wenn er angegeben ist.

Grundlegende Auth-Informationen (veraltet) :

Der Autorisierungsheader wird aus dem Feld Benutzername und Kennwort (oder nur Benutzername) des WebSocket-URI generiert:

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

Das Obige führt zu folgendem Header mit der Zeichenfolge "Benutzername: Passwort" base64-codiert:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Ich habe die Basisauthentifizierung in Chrome 55 und Firefox 50 getestet und überprüft, dass die Basisauthentifizierungsinformationen tatsächlich mit dem Server ausgehandelt werden (dies funktioniert möglicherweise nicht in Safari).

Vielen Dank an Dmitry Frank für die grundlegende Antwort

Kanaka
quelle
37
Ich bin auf das gleiche Problem gestoßen. Schade, dass diese Standards so schlecht integriert sind. Sie würden erwarten, dass sie sich die XHR-API ansehen, um Anforderungen (da WebSockets und XHR zusammenhängen) für die WebSockets-API zu finden, aber es scheint, dass sie die API nur auf einer Insel selbst entwickeln.
Eleotlecram
4
@eleotlecram, treten Sie der HyBi-Arbeitsgruppe bei und schlagen Sie sie vor. Die Gruppe ist für die Öffentlichkeit zugänglich und es wird derzeit an nachfolgenden Versionen des Protokolls gearbeitet.
Kanaka
5
@Charlie: Wenn Sie den Server vollständig steuern, ist dies eine Option. Der üblichere Ansatz besteht darin, ein Ticket / Token von Ihrem normalen HTTP-Server zu generieren und dann den Client das Ticket / Token senden zu lassen (entweder als Abfragezeichenfolge im Websocket-Pfad oder als erste Websocket-Nachricht). Der Websocket-Server überprüft dann, ob das Ticket / Token gültig ist (nicht abgelaufen, noch nicht verwendet, von derselben IP-Adresse wie beim Erstellen usw.). Ich glaube auch, dass die meisten Websockets-Clients die grundlegende Authentifizierung unterstützen (möglicherweise nicht genug für Sie). Weitere Informationen: devcenter.heroku.com/articles/websocket-security
kanaka
3
Ich denke, es ist beabsichtigt. Ich habe den Eindruck, dass die Implementierung absichtlich von HTTP übernommen wird, diese jedoch vom Design her so weit wie möglich getrennt hält. Der Text in der Spezifikation geht weiter: "Das Design beschränkt WebSocket jedoch nicht auf HTTP, und zukünftige Implementierungen könnten einen einfacheren Handshake über einen dedizierten Port verwenden, ohne das gesamte Protokoll neu zu erfinden. Dieser letzte Punkt ist wichtig, da die Verkehrsmuster des interaktiven Messaging dies tun." entspricht nicht genau dem Standard-HTTP-Verkehr und kann bei einigen Komponenten zu ungewöhnlichen Belastungen führen. "
3
Leider scheint dies in Edge nicht zu funktionieren. Danke, MS: /
Geschwister
40

Eher eine alternative Lösung, aber alle modernen Browser senden die Domain-Cookies zusammen mit der Verbindung. Verwenden Sie dazu:

var authToken = 'R3YKZFKBVi';

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

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

Am Ende die Anforderungsverbindungsheader:

Cookie: X-Authorization=R3YKZFKBVi
Tim
quelle
1
Dies scheint der beste Weg zu sein, um Zugriffstoken bei Verbindung zu übergeben. Im Moment.
Cammil
1
Was ist, wenn sich der WS-Server-URI vom Client-URI unterscheidet?
Dänisch
35

Das Problem mit dem HTTP-Autorisierungsheader kann wie folgt behoben werden:

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

Anschließend wird ein geeigneter HTTP-Header für die Basisautorisierung mit dem bereitgestellten usernameund festgelegt password. Wenn Sie eine Basisautorisierung benötigen, sind Sie fertig.


Ich möchte Bearerjedoch verwenden und habe auf den folgenden Trick zurückgegriffen: Ich verbinde mich wie folgt mit dem Server:

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

Und wenn mein Code auf der Serverseite den Header "Basic Authorization" mit einem nicht leeren Benutzernamen und einem leeren Kennwort erhält, interpretiert er den Benutzernamen als Token.

Dmitry Frank
quelle
12
Ich versuche die von Ihnen vorgeschlagene Lösung. Ich kann jedoch nicht sehen, dass der Autorisierungsheader zu meiner Anfrage hinzugefügt wird. Ich habe es mit verschiedenen Browsern versucht, z. B. Chrome V56, Firefox V51.0. Ich verwende meinen Server auf meinem lokalen Host. Die URL des Websockets lautet also "ws: // myusername: mypassword @ localhost: 8080 / mywebsocket". Irgendeine Idee, was falsch sein könnte? Danke
LearnToLive
4
Ist es sicher, Token über URL zu übertragen?
Mergasov
2
Leerer / ignorierter Benutzername und nicht leeres Passwort als Token sind möglicherweise besser, da Benutzernamen möglicherweise protokolliert werden.
AndreKR
9
Ich stimme @LearnToLive zu - ich habe dies mit wss (z. B. wss://user:[email protected]/ws) verwendet und Authorizationauf der Serverseite keinen Header erhalten (mit Chrome Version 60)
user9645
6
Ich habe das gleiche Problem wie @LearnToLive und @ user9645; Weder Chrome noch Firefox fügen den Autorisierungsheader hinzu, wenn der URI im wss://user:pass@hostFormat vorliegt. Wird dies von Browsern nicht unterstützt oder läuft beim Handshake etwas schief?
David Kaczynski
19

Sie können keine Header hinzufügen. Wenn Sie jedoch zum Zeitpunkt der Verbindung nur Werte an den Server übergeben müssen, können Sie einen Teil der Abfragezeichenfolge in der URL angeben:

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

Diese URL ist gültig, aber Sie müssen natürlich Ihren Servercode ändern, um ihn zu analysieren.

Gabriele Carioli
quelle
14
Bei dieser Lösung muss vorsichtig vorgegangen werden. Die Abfragezeichenfolge kann abgefangen, in Proxys usw. angemeldet werden, sodass die Weitergabe vertraulicher Informationen (Benutzer / Kennwort / Authentifizierungstoken) auf diese Weise nicht sicher genug ist.
Nir
5
@Nir mit WSS sollte der Querystring wahrscheinlich sicher sein
Sebastien Lorber
8
ws ist einfacher Text. Mit dem ws-Protokoll kann alles abgefangen werden.
Gabriele Carioli
1
@SebastienLorber Es ist nicht sicher, eine Abfragezeichenfolge zu verwenden. Es wird nicht verschlüsselt. Dies gilt auch für HTTPS. Da jedoch das Protokoll "ws: // ..." verwendet wird, spielt es keine Rolle.
Lu4
5
@ Lu4 Die Abfragezeichenfolge ist verschlüsselt, aber es gibt eine Reihe anderer Gründe, vertrauliche Daten nicht als URL-Abfrageparameter hinzuzufügen. Stackoverflow.com/questions/499591/are-https-urls-encrypted/… & blog.httpwatch.com/2009 /
16

Sie können keinen benutzerdefinierten Header senden, wenn Sie eine WebSockets-Verbindung mithilfe der JavaScript-WebSockets-API herstellen möchten. Sie können SubprotocolsHeader verwenden, indem Sie den zweiten WebSocket-Klassenkonstruktor verwenden:

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

Anschließend können Sie die Subprotokoll-Header mithilfe des Sec-WebSocket-ProtocolSchlüssels auf dem Server abrufen .

Es gibt auch eine Einschränkung: Ihre Header-Werte für Unterprotokolle dürfen kein Komma ( ,) enthalten!

Saeed Zarinfam
quelle
1
Darf ein JWT ein Komma enthalten?
CESCO
1
Ich glaube nicht. JWT besteht aus drei Base64-codierten Nutzdaten, die jeweils durch einen Punkt getrennt sind. Ich glaube, dies schließt die Möglichkeit eines Kommas aus.
BillyBBone
2
Ich habe das implementiert und es funktioniert - fühlt sich einfach komisch an. danke
BatteryAcid
3
Schlagen Sie vor, dass wir den Sec-WebSocket-ProtocolHeader als Alternative zum AuthorizationHeader verwenden?
rrw
13

Das Senden eines Autorisierungsheaders ist nicht möglich.

Das Anhängen eines Token-Abfrageparameters ist eine Option. Unter bestimmten Umständen kann es jedoch unerwünscht sein, Ihr Hauptanmeldetoken im Klartext als Abfrageparameter zu senden, da es undurchsichtiger als die Verwendung eines Headers ist und überall protokolliert wird. Wenn dies Sicherheitsbedenken für Sie aufwirft, besteht eine Alternative darin, ein sekundäres JWT-Token nur für das Web-Socket-Material zu verwenden .

Erstellen Sie einen REST-Endpunkt zum Generieren dieser JWT , auf den natürlich nur Benutzer zugreifen können, die mit Ihrem primären Anmeldetoken authentifiziert sind (über Header übertragen). Das Web-Socket-JWT kann anders konfiguriert werden als Ihr Anmeldetoken, z. B. mit einem kürzeren Zeitlimit. Daher ist es sicherer, es als Abfrageparameter Ihrer Upgrade-Anfrage zu senden.

Erstellen Sie einen separaten JwtAuthHandler für dieselbe Route, auf der Sie den SockJS eventbusHandler registrieren . Stellen Sie sicher, dass Ihr Auth-Handler zuerst registriert ist, damit Sie das Web-Socket-Token mit Ihrer Datenbank vergleichen können (das JWT sollte irgendwie mit Ihrem Benutzer im Backend verknüpft sein).

Norbert Schöpke
quelle
Dies war die einzige sichere Lösung, die ich für API Gateway-Websockets finden konnte. Slack macht etwas Ähnliches mit ihrer RTM-API und sie haben eine Zeitüberschreitung von 30 Sekunden.
Andrhamm
2

Dank Kanakas Antwort wurde es total so gehackt.

Klient:

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

Server (in diesem Beispiel mit Koa2, sollte aber überall ähnlich sein):

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
quelle
4
Übergibt dies nicht einfach Ihr Token in dem Abschnitt, in dem Ihr Client ein oder mehrere spezifische Protokolle anfordern soll? Ich kann das zum Laufen bringen, auch kein Problem, aber ich habe mich entschieden, dies nicht zu tun und lieber das zu tun, was Motes vorgeschlagen hat, und zu blockieren, bis das Authentifizierungstoken auf onOpen () gesendet wird. Das Überladen des Protokollanforderungsheaders scheint mir falsch zu sein, und da meine API für den öffentlichen Gebrauch bestimmt ist, denke ich, dass dies für die Verbraucher meiner API etwas verwirrend sein wird.
Jay
0

Mein Fall:

  • Ich möchte eine Verbindung zu einem Produktions-WS-Server herstellen. A. www.mycompany.com/api/ws .
  • mit echten Anmeldeinformationen (ein Sitzungscookie) ...
  • von einer lokalen Seite ( localhost:8000).

Die Einstellung document.cookie = "sessionid=foobar;path=/"hilft nicht, da die Domänen nicht übereinstimmen.

Die Lösung :

Hinzufügen 127.0.0.1 wsdev.company.comzu /etc/hosts.

Auf diese Weise verwendet Ihr Browser Cookies, mycompany.comwenn www.mycompany.com/api/wsSie eine Verbindung zu einer gültigen Subdomain herstellen wsdev.company.com.

Max Malysh
quelle
-1

In meiner Situation (Azure Time Series Insights wss: //)

Mit dem ReconnectingWebsocket-Wrapper konnten mit einer einfachen Lösung Header hinzugefügt werden:

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

Wo Nutzlast in diesem Fall ist:

{
  "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
quelle
-3

Technisch gesehen senden Sie diese Header vor der Protokollaktualisierungsphase über die Verbindungsfunktion. Das hat bei mir in einem nodejsProjekt funktioniert :

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
George
quelle
3
Dies gilt für den Websocket-Client in npm (für Knoten). npmjs.com/package/websocket Insgesamt wäre dies genau das, wonach ich suche, aber im Browser.
Arnuschky
1
Es wurde herabgestuft, weil dieser Header-Parameter in der WebSocket-Protokollschicht enthalten ist und die Frage sich auf HTTP-Header bezieht.
Toilette
" headerssollte entweder null oder ein Objekt sein, das zusätzliche beliebige HTTP-Anforderungsheader angibt, die zusammen mit der Anforderung gesendet werden sollen." von WebSocketClient.md ; Daher ist headershier die HTTP-Schicht.
Momocow
Auch alle , die benutzerdefinierten Header zur Verfügung zu stellen möge , sollten im Auge behalten , um die Funktion der Unterzeichnung des connectVerfahrens beschrieben , wie connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), dh die headerssollten zusammen mit zur Verfügung gestellt werden requestOptions, zum Beispiel ws.connect(url, '', headers, null). originIn diesem Fall kann nur die Zeichenfolge ignoriert werden.
Momocow
-4

Sie können die Header als Schlüsselwert im dritten Parameter (Optionen) innerhalb eines Objekts übergeben. Beispiel mit Autorisierungstoken. Belassen Sie das Protokoll (zweiter Parameter) als null

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

Bearbeiten: Scheint, dass dieser Ansatz nur mit der NodeJS-Bibliothek funktioniert, nicht mit der Standard-Browser-Implementierung. Verlassen Sie es, weil es für manche Menschen nützlich sein könnte.

Nodens
quelle
Hatte meine Hoffnungen für eine Sekunde. Es scheint keine Überlastung zu geben, die einen dritten Parameter in WebSocket ctor benötigt.
Levitikon
Ich habe die Idee von wscat code. github.com/websockets/wscat/blob/master/bin/wscat Zeile 261, die das ws-Paket verwendet. Dachte, dies sei eine Standardverwendung.
Nodens