En-têtes HTTP dans l'API client Websockets

201

Il semble qu'il soit facile d'ajouter des en-têtes HTTP personnalisés à votre client Websocket avec n'importe quel client d'en-tête HTTP qui prend en charge cela, mais je ne trouve pas comment le faire avec l'API JSON.

Pourtant, il semble que ces en-têtes devraient être pris en charge dans la spécification .

Quelqu'un a une idée de comment y parvenir?

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

Plus précisément, je dois être en mesure d'envoyer un en-tête d'autorisation HTTP.

Julien Genestoux
la source
14
Je pense qu'une bonne solution est de permettre au WebSocket de se connecter sans autorisation, mais ensuite de bloquer et d'attendre sur le serveur pour recevoir l'autorisation du webSocket qui transmettra les informations d'autorisation dans son événement onopen.
Motes
La suggestion de @Motes semble être la meilleure solution. Il était très facile de faire un appel d'autorisation depuis onOpen qui vous permet d'accepter / rejeter le socket en fonction de la réponse d'autorisation. J'ai d'abord tenté d'envoyer un jeton d'authentification dans l'en-tête Sec-WebSocket-Protocol, mais cela ressemble à un hack.
BatteryAcid
@Motes Salut, pourriez-vous expliquer la partie "bloquer et attendre sur le serveur"? vous voulez dire quelque chose comme ne pas traiter les messages jusqu'à ce qu'il y ait un message "d'authentification"?
Himal
@Himal, oui, la conception du serveur ne doit pas envoyer de données ni accepter d'autres données que l'autorisation au début de la connexion.
Motes
@Motes Merci pour la réponse. J'ai été un peu confus par la partie de blocage, car je crois comprendre que vous ne pouvez pas bloquer la connectdemande initiale . J'utilise des canaux Django sur le back-end et je l'ai conçu pour accepter la connexion lors de l' connectévénement. il définit ensuite un indicateur "is_auth" dans l' receiveévénement (s'il voit un message d'authentification valide). si l'indicateur is_auth n'est pas défini et qu'il ne s'agit pas d'un message d'authentification, il ferme la connexion.
Himal

Réponses:

212

Mise à jour 2x

Réponse courte: Non, seuls le chemin et le champ de protocole peuvent être spécifiés.

Réponse plus longue:

Il n'y a pas de méthode dans l' API WebSockets JavaScript pour spécifier des en-têtes supplémentaires pour le client / navigateur à envoyer. Le chemin HTTP ("GET / xyz") et l'en-tête de protocole ("Sec-WebSocket-Protocol") peuvent être spécifiés dans le constructeur WebSocket.

L'en-tête Sec-WebSocket-Protocol (qui est parfois étendu pour être utilisé dans l'authentification spécifique à websocket) est généré à partir du deuxième argument facultatif vers le constructeur WebSocket:

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

Les résultats ci-dessus donnent les en-têtes suivants:

Sec-WebSocket-Protocol: protocol

et

Sec-WebSocket-Protocol: protocol1, protocol2

Un modèle courant pour obtenir une authentification / autorisation WebSocket consiste à implémenter un système de billetterie où la page hébergeant le client WebSocket demande un ticket au serveur, puis transmet ce ticket lors de la configuration de la connexion WebSocket soit dans la chaîne URL / requête, dans le champ de protocole, ou requis comme premier message après l'établissement de la connexion. Le serveur n'autorise alors la connexion à se poursuivre que si le ticket est valide (existe, n'a pas déjà été utilisé, IP client encodée dans les correspondances de ticket, l'horodatage du ticket est récent, etc.). Voici un résumé des informations de sécurité WebSocket: https://devcenter.heroku.com/articles/websocket-security

L'authentification de base était auparavant une option, mais elle est obsolète et les navigateurs modernes n'envoient pas l'en-tête même s'il est spécifié.

Informations d'authentification de base (obsolète) :

L'en-tête d'autorisation est généré à partir du champ nom d'utilisateur et mot de passe (ou simplement nom d'utilisateur) de l'URI WebSocket:

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

Les résultats ci-dessus dans l'en-tête suivant avec la chaîne "nom d'utilisateur: mot de passe" codé en base64:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

J'ai testé l'authentification de base dans Chrome 55 et Firefox 50 et vérifié que les informations d'authentification de base sont bien négociées avec le serveur (cela peut ne pas fonctionner dans Safari).

Merci à Dmitry Frank pour la réponse d' authentification de base

Kanaka
la source
38
J'ai rencontré le même problème. Dommage que ces normes soient si mal intégrées. Vous vous attendez à ce qu'ils regardent l'API XHR pour trouver les exigences (puisque WebSockets et XHR sont liées) pour l'API WebSockets, mais il semble qu'ils développent juste l'API sur une île par lui-même.
eleotlecram
4
@eleotlecram, rejoignez le groupe de travail HyBi et proposez-le. Le groupe est ouvert au public et des travaux sont en cours pour les versions ultérieures du protocole.
kanaka
5
@Charlie: si vous contrôlez entièrement le serveur, c'est une option. L'approche la plus courante consiste à générer un ticket / jeton à partir de votre serveur HTTP normal, puis à envoyer le client / ticket (soit sous la forme d'une chaîne de requête dans le chemin du Websocket, soit en tant que premier message Websocket). Le serveur websocket valide ensuite que le ticket / token est valide (n'a pas expiré, n'a pas déjà été utilisé, provenant de la même IP que lors de sa création, etc.). En outre, je crois que la plupart des clients Websockets prennent en charge l'authentification de base (peut-être pas assez pour vous cependant). Plus d'infos: devcenter.heroku.com/articles/websocket-security
kanaka
3
Je suppose que c'est par conception. J'ai l'impression que l'implémentation emprunte intentionnellement à HTTP mais les garde séparés autant que possible par conception. Le texte de la spécification continue: "Cependant, la conception ne limite pas WebSocket à HTTP, et les futures implémentations pourraient utiliser une prise de contact plus simple sur un port dédié sans réinventer l'ensemble du protocole. Ce dernier point est important car les modèles de trafic de la messagerie interactive ne ne correspondent pas étroitement au trafic HTTP standard et peuvent induire des charges inhabituelles sur certains composants. "
3
Malheureusement, cela ne semble pas fonctionner dans Edge. Merci, MS: /
sibbl
40

Plus d'une solution alternative, mais tous les navigateurs modernes envoient les cookies de domaine avec la connexion, donc en utilisant:

var authToken = 'R3YKZFKBVi';

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

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

Terminez avec les en-têtes de connexion de demande:

Cookie: X-Authorization=R3YKZFKBVi
Tim
la source
1
Cela semble être le meilleur moyen de passer des jetons d'accès lors de la connexion. En ce moment.
cammil
2
que faire si l'URI du serveur WS est différent de l'URI du client?
danois
@Danish Eh bien, cela ne fonctionne pas, car vous ne pouvez pas définir de cookies pour les autres domaines côté client
Tofandel
35

Le problème d'en-tête d'autorisation HTTP peut être résolu comme suit:

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

Ensuite, un en-tête HTTP d'autorisation de base approprié sera défini avec les éléments fournis usernameet password. Si vous avez besoin d'une autorisation de base, vous êtes prêt.


Je veux Bearercependant utiliser , et j'ai recours à l'astuce suivante: je me connecte au serveur comme suit:

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

Et lorsque mon code côté serveur reçoit un en-tête d'autorisation de base avec un nom d'utilisateur non vide et un mot de passe vide, il interprète le nom d'utilisateur comme un jeton.

Dmitry Frank
la source
12
J'essaie la solution que vous proposez. Mais je ne peux pas voir l'en-tête d'autorisation ajouté à ma demande. Je l'ai essayé en utilisant différents navigateurs, par exemple Chrome V56, Firefox V51.0. J'exécute mon serveur sur mon hôte local. ainsi l'url du websocket est "ws: // myusername: mypassword @ localhost: 8080 / mywebsocket". Une idée de ce qui pourrait être faux? Merci
LearnToLive
4
Est-il sûr de transférer un jeton via une URL?
Mergasov
2
Un nom d'utilisateur vide / ignoré et un mot de passe non vide en tant que jeton pourraient être meilleurs car les noms d'utilisateur peuvent être enregistrés.
AndreKR
9
Je suis d'accord avec @LearnToLive - J'ai utilisé cela avec wss (par exemple wss://user:[email protected]/ws) et je n'ai eu aucun en- Authorizationtête côté serveur (en utilisant Chrome version 60)
user9645
6
J'ai le même problème que @LearnToLive et @ user9645; ni chrome ni firefox n'ajoutent l'en-tête d'autorisation lorsque l'URI est au wss://user:pass@hostformat. N'est-ce pas pris en charge par les navigateurs, ou est-ce que quelque chose ne va pas avec la poignée de main?
David Kaczynski
19

Vous ne pouvez pas ajouter d'en-têtes mais, si vous avez juste besoin de transmettre des valeurs au serveur au moment de la connexion, vous pouvez spécifier une partie de chaîne de requête sur l'URL:

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

Cette URL est valide mais - bien sûr - vous devrez modifier le code de votre serveur pour l'analyser.

Gabriele Carioli
la source
14
vous devez être prudent avec cette solution, la chaîne de requête peut être interceptée, connectée à des proxys, etc.
Nir
5
@Nir avec WSS, la chaîne de requête devrait probablement être en sécurité
Sebastien Lorber
8
ws est en texte brut. En utilisant le protocole ws, tout peut être intercepté.
Gabriele Carioli
1
@SebastienLorber il n'est pas sûr d'utiliser une chaîne de requête, il n'est pas chiffré de la même manière s'applique au HTTPS, mais puisque le protocole "ws: // ..." est utilisé, cela n'a vraiment pas d'importance.
Lu4
5
@ Lu4, la chaîne de requête est cryptée, mais il existe une multitude d'autres raisons de ne pas ajouter de données sensibles en tant que paramètres de requête URL stackoverflow.com/questions/499591/are-https-urls-encrypted/… & blog.httpwatch.com/2009 /
16

Vous ne pouvez pas envoyer d'en-tête personnalisé lorsque vous souhaitez établir une connexion WebSockets à l'aide de l'API WebSockets JavaScript. Vous pouvez utiliser des en- Subprotocolstêtes à l'aide du deuxième constructeur de classe WebSocket:

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

puis vous pouvez obtenir les en-têtes Subprotocols en utilisant Sec-WebSocket-Protocol clé sur le serveur.

Il y a aussi une limitation, vos valeurs d'en-têtes Subprotocols ne peuvent pas contenir de virgule ( ,)!

Saeed Zarinfam
la source
1
Un Jwt peut-il contenir une virgule?
CESCO
1
Je ne le crois pas. JWT se compose de trois charges utiles codées en base64, chacune séparée par une période. Je crois que cela exclut la possibilité d'une virgule.
BillyBBone
2
J'ai implémenté cela et cela fonctionne - ça fait juste bizarre. merci
BatteryAcid
3
proposez-vous que nous utilisons l'en- Sec-WebSocket-Protocoltête comme alternative à l'en- Authorizationtête?
rrw
13

L'envoi de l'en-tête d'autorisation n'est pas possible.

Attacher un paramètre de requête de jeton est une option. Cependant, dans certaines circonstances, il peut être indésirable d'envoyer votre jeton de connexion principal en texte brut en tant que paramètre de requête, car il est plus opaque que d'utiliser un en-tête et finira par être enregistré whoknowswhere. Si cela soulève des problèmes de sécurité pour vous, une alternative consiste à utiliser un jeton JWT secondaire uniquement pour le socket Web. .

Créer un point de terminaison REST pour générer ce JWT , auquel seuls les utilisateurs authentifiés avec votre jeton de connexion principal (transmis via l'en-tête) peuvent bien sûr accéder. Le socket Web JWT peut être configuré différemment de votre jeton de connexion, par exemple avec un délai plus court, il est donc plus sûr de l'envoyer comme paramètre de requête de votre demande de mise à niveau.

Créez un JwtAuthHandler distinct pour le même itinéraire que celui sur lequel vous enregistrez le bus d'événements SockJS . Assurez-vous que votre gestionnaire d'authentification est enregistré en premier, afin que vous puissiez vérifier le jeton de socket Web par rapport à votre base de données (le JWT devrait être en quelque sorte lié à votre utilisateur dans le backend).

Norbert Schöpke
la source
C'était la seule solution sécurisée que je pouvais trouver pour les sockets Web API Gateway. Slack fait quelque chose de similaire avec leur API RTM et ils ont un délai d'attente de 30 secondes.
andrhamm
2

Totalement piraté comme ça, grâce à la réponse de Kanaka.

Client:

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

Serveur (en utilisant Koa2 dans cet exemple, mais devrait être similaire partout):

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
la source
4
Cela ne passe-t-il pas simplement votre jeton dans la section où votre client est censé demander un ou plusieurs protocoles spécifiques? Je peux faire fonctionner cela, pas de problème aussi, mais j'ai décidé de ne pas le faire et plutôt de faire ce que Motes a suggéré et de bloquer jusqu'à ce que le jeton d'authentification soit envoyé sur onOpen (). Surcharger l'en-tête de demande de protocole me semble mauvais, et comme mon API est destinée à la consommation publique, je pense que cela va être un peu déroutant pour les consommateurs de mon API.
Jay
0

Mon cas:

  • Je veux me connecter à un serveur WS de production a www.mycompany.com/api/ws ...
  • en utilisant des informations d'identification réelles (un cookie de session) ...
  • à partir d'une page locale ( localhost:8000).

Réglage document.cookie = "sessionid=foobar;path=/" n'aidera pas car les domaines ne correspondent pas

La solution :

Ajouter 127.0.0.1 wsdev.company.comà /etc/hosts.

De cette façon, votre navigateur utilisera les cookies mycompany.comlors de la connexion à www.mycompany.com/api/wscar vous vous connectez à partir d'un sous-domaine valide wsdev.company.com.

Max Malysh
la source
-1

Dans ma situation (Azure Time Series Insights wss: //)

En utilisant le wrapper ReconnectingWebsocket et a réussi à ajouter des en-têtes avec une solution simple:

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

Dans ce cas, la charge utile est:

{
  "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
la source
-3

Techniquement, vous enverrez ces en-têtes via la fonction de connexion avant la phase de mise à niveau du protocole. Cela a fonctionné pour moi dans un nodejsprojet:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
George
la source
3
C'est pour le client websocket dans npm (pour le nœud). npmjs.com/package/websocket Globalement, ce serait exactement ce que je recherche, mais dans le navigateur.
arnuschky
1
Il est rétrogradé parce que ce paramètre d'en-têtes se trouve sur la couche de protocole WebSocket, et la question concerne les en-têtes HTTP.
Toilal
" headersdevrait être nul ou un objet spécifiant des en-têtes de requête HTTP arbitraires supplémentaires à envoyer avec la requête." de WebSocketClient.md ; par conséquent, la headerscouche ici est HTTP.
momocow
De plus, toute personne qui souhaite fournir des en-têtes personnalisés doit garder à l'esprit la signature de fonction de la connectméthode, décrite comme connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), c'est-à-dire qu'elle headersdoit être fournie avec requestOptions, par exemple ws.connect(url, '', headers, null),. Seule la originchaîne peut être ignorée dans ce cas.
momocow
-4

Vous pouvez passer les en-têtes comme valeur-clé dans le troisième paramètre (options) à l'intérieur d'un objet. Exemple avec jeton d'autorisation. Laissé le protocole (deuxième paramètre) comme nul

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

Edit: Il semble que cette approche ne fonctionne qu'avec la bibliothèque nodejs et non avec l'implémentation de navigateur standard. Le laisser parce qu'il pourrait être utile à certaines personnes.

Nodens
la source
J'ai eu mes espoirs pendant une seconde. Il ne semble pas y avoir de surcharge prenant un 3ème paramètre dans WebSocket ctor.
Levitikon
J'ai eu l'idée du code wscat. github.com/websockets/wscat/blob/master/bin/wscat ligne 261 qui utilise le package ws. Je pensais que c'était une utilisation standard.
Nodens