Cabeçalhos HTTP na API do cliente Websockets

201

Parece fácil adicionar cabeçalhos HTTP personalizados ao seu cliente de websocket com qualquer cliente de cabeçalho HTTP que suporte isso, mas não consigo encontrar como fazê-lo com a API JSON.

No entanto, parece que deve haver suporte para esses cabeçalhos nas especificações .

Alguém tem uma idéia de como alcançá-lo?

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

Especificamente, preciso enviar um cabeçalho de autorização HTTP.

Julien Genestoux
fonte
14
Eu acho que uma boa solução é permitir que o WebSocket se conecte sem autorização, mas depois bloqueie e aguarde o servidor para receber a autorização do webSocket que transmitirá as informações de autorização em seu evento onopen.
quer
A sugestão do @Motes parece ser a melhor opção. Foi muito fácil fazer uma chamada de autorização do onOpen, que permite aceitar / rejeitar o soquete com base na resposta da autorização. Originalmente, tentei enviar token de autenticação no cabeçalho Sec-WebSocket-Protocol, mas isso parece um hack.
BatteryAcid
@Motes Oi, você poderia explicar a parte "bloquear e aguardar no servidor"? você quer dizer algo como não processar nenhuma mensagem até que haja uma mensagem "auth"?
Himal
@Himal, sim, o design do servidor não deve enviar dados nem aceitar outros dados além da autorização no início da conexão.
Motes
@Motes Obrigado pela resposta. Fiquei um pouco confuso com a parte de bloqueio, porque, pelo que entendi, você não pode bloquear a connectsolicitação inicial . Estou usando os canais do Django no back-end e o projetei para aceitar a conexão no connectevento. em seguida, define um sinalizador "is_auth" no receiveevento (se houver uma mensagem de autenticação válida). se o sinalizador is_auth não estiver definido e não for uma mensagem de autenticação, fechará a conexão.
Himal

Respostas:

211

Atualizado 2x

Resposta curta: Não, apenas o caminho e o campo do protocolo podem ser especificados.

Resposta mais longa:

Não há método na API JavaScript WebSockets para especificar cabeçalhos adicionais para o cliente / navegador enviar. O caminho HTTP ("GET / xyz") e o cabeçalho do protocolo ("Sec-WebSocket-Protocol") podem ser especificados no construtor WebSocket.

O cabeçalho Sec-WebSocket-Protocol (que às vezes é estendido para ser usado na autenticação específica do websocket) é gerado a partir do segundo argumento opcional para o construtor WebSocket:

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

O resultado acima resulta nos seguintes cabeçalhos:

Sec-WebSocket-Protocol: protocol

e

Sec-WebSocket-Protocol: protocol1, protocol2

Um padrão comum para obter autenticação / autorização do WebSocket é implementar um sistema de emissão de bilhetes, em que a página que hospeda o cliente WebSocket solicita um ticket do servidor e depois passa esse ticket durante a configuração da conexão do WebSocket na URL / string de consulta, no campo protocolo, ou necessário como a primeira mensagem após o estabelecimento da conexão. O servidor só permite que a conexão continue se o ticket for válido (existe, ainda não foi usado, o IP do cliente codificado nas correspondências do ticket, o registro de data e hora do ticket é recente, etc.). Aqui está um resumo das informações de segurança do WebSocket: https://devcenter.heroku.com/articles/websocket-security

A autenticação básica era anteriormente uma opção, mas isso foi preterido e os navegadores modernos não enviam o cabeçalho, mesmo que seja especificado.

Informações básicas de autenticação (descontinuado) :

O cabeçalho de autorização é gerado a partir do campo nome de usuário e senha (ou apenas nome de usuário) do URI do WebSocket:

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

O resultado acima resulta no seguinte cabeçalho com a cadeia "nome de usuário: senha" codificada em base64:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Testei a autenticação básica no Chrome 55 e Firefox 50 e verifiquei que as informações básicas de autenticação são realmente negociadas com o servidor (isso pode não funcionar no Safari).

Agradecimentos a Dmitry Frank's pela resposta básica de autenticação

kanaka
fonte
38
Eu me deparei com o mesmo problema. Pena que esses padrões sejam tão mal integrados. Você esperaria que eles observassem a API XHR para encontrar requisitos (já que WebSockets e XHR estão relacionados) para a API WebSockets, mas parece que eles estão apenas desenvolvendo a API em uma ilha por si só.
Eleotlecram
4
@eleotlecram, participe do grupo de trabalho HyBi e proponha-o. O grupo é aberto ao público e há trabalho em andamento para versões subseqüentes do protocolo.
kanaka
5
@ Charlie: se você controla totalmente o servidor, essa é uma opção. A abordagem mais comum é gerar um ticket / token a partir do servidor HTTP normal e solicitar ao cliente que envie o ticket / token (como uma string de consulta no caminho do websocket ou como a primeira mensagem do websocket). O servidor websocket então valida que o ticket / token é válido (não expirou, ainda não foi usado, proveniente do mesmo IP de quando criado, etc). Além disso, acredito que a maioria dos clientes de websockets suporta autenticação básica (embora isso não seja suficiente para você). Mais informações: devcenter.heroku.com/articles/websocket-security
kanaka
3
Eu acho que é por design. Tenho a impressão de que a implementação empresta intencionalmente o HTTP, mas os mantém separados o máximo possível por design. O texto na área específica continua: "No entanto, o design não limita o WebSocket ao HTTP, e as implementações futuras podem usar um handshake mais simples em uma porta dedicada sem reinventar o protocolo inteiro. Esse último ponto é importante porque os padrões de tráfego das mensagens interativas não não corresponde exatamente ao tráfego HTTP padrão e pode induzir cargas incomuns em alguns componentes ".
3
Infelizmente, isso não parece funcionar no Edge. Obrigado, MS: /
sibbl
40

Mais uma solução alternativa, mas todos os navegadores modernos enviam os cookies do domínio junto com a conexão, usando:

var authToken = 'R3YKZFKBVi';

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

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

Termine com os cabeçalhos de conexão da solicitação:

Cookie: X-Authorization=R3YKZFKBVi
Tim
fonte
1
Essa parece ser a melhor maneira de transmitir tokens de acesso na conexão. No momento.
cammil 23/02
1
e se o URI do servidor WS for diferente do URI do cliente?
Dinamarquês
35

O problema do cabeçalho de autorização HTTP pode ser solucionado com o seguinte:

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

Em seguida, um cabeçalho HTTP de autorização básica adequado será definido com o fornecido usernamee password. Se você precisa de Autorização Básica, está tudo pronto.


No Bearerentanto, quero usar e recorri ao seguinte truque: Eu me conecto ao servidor da seguinte maneira:

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

E quando meu código no servidor recebe o cabeçalho de Autorização Básica com nome de usuário não vazio e senha vazia, ele interpreta o nome de usuário como um token.

Dmitry Frank
fonte
12
Estou tentando a solução sugerida por você. Mas não consigo ver o cabeçalho da autorização sendo adicionado à minha solicitação. Eu tentei usando navegadores diferentes, por exemplo, Chrome V56, Firefox V51.0 Estou executando meu servidor no meu host local. portanto, o URL do websocket é "ws: // myusername: mypassword @ localhost: 8080 / mywebsocket". Alguma idéia do que pode estar errado? Obrigado
LearnToLive
4
É seguro transferir token por meio de URL?
Mergasov 4/17/17
2
Nome de usuário vazio / ignorado e senha não vazia como token podem ser melhores porque os nomes de usuário podem ser registrados.
AndreKR
9
Concordo com @LearnToLive - Eu usei isso com WSS (por exemplo wss://user:[email protected]/ws) e não obteve Authorizationcabeçalho no lado do servidor (usando o Chrome versão 60)
user9645
6
Eu tenho o mesmo problema que @LearnToLive e @ user9645; nem chrome nem firefox estão adicionando o cabeçalho de autorização quando o URI está no wss://user:pass@hostformato. Isso não é suportado pelos navegadores ou algo está errado com o aperto de mão?
David Kaczynski
19

Você não pode adicionar cabeçalhos, mas se precisar passar valores ao servidor no momento da conexão, poderá especificar uma parte da cadeia de caracteres de consulta no URL:

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

Esse URL é válido, mas - é claro - você precisará modificar o código do servidor para analisá-lo.

Gabriele Carioli
fonte
14
precisa ter cuidado com esta solução, a string de consulta pode ser interceptada, proxies logados etc.
Nir
5
@Nir com WSS a querystring provavelmente deve ser seguro
Sebastien Lorber
8
ws é texto simples. Usando o protocolo ws, qualquer coisa pode ser interceptada.
Gabriele Carioli
1
@SebastienLorber não é seguro usar a string de consulta, não está sendo criptografada o mesmo se aplica ao HTTPS, mas como o protocolo "ws: // ..." é usado, isso realmente não importa.
Lu4 20/09/17
5
@ Lu4, a cadeia de caracteres da consulta é criptografada, mas existem vários outros motivos para não adicionar dados confidenciais como parâmetros de consulta da URL stackoverflow.com/questions/499591/are-https-urls-encrypted/… & blog.httpwatch.com/2009 /
16

Você não pode enviar cabeçalho personalizado quando desejar estabelecer uma conexão WebSockets usando a API JavaScript WebSockets. Você pode usar Subprotocolscabeçalhos usando o segundo construtor de classe WebSocket:

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

e então você pode obter os cabeçalhos de subprotocolo usando Sec-WebSocket-Protocol chave no servidor.

Há também uma limitação: os valores dos cabeçalhos dos subprotocolo não podem conter vírgula ( ,)!

Saeed Zarinfam
fonte
1
Um Jwt pode conter vírgula?
CESCO
1
Eu não acredito nisso. O JWT consiste em três cargas úteis codificadas em base64, cada uma separada por um período. Eu acredito que isso descarta a possibilidade de vírgula.
BillyBBone
2
Eu implementei isso e funciona - parece estranho. Obrigado,
BatteryCid
3
você está sugerindo que usemos o Sec-WebSocket-Protocolcabeçalho como alternativa ao Authorizationcabeçalho?
Rrw 14/05
13

O cabeçalho de autorização de envio não é possível.

Anexar um parâmetro de consulta de token é uma opção. No entanto, em algumas circunstâncias, pode ser indesejável enviar seu token de logon principal em texto sem formatação como um parâmetro de consulta, porque é mais opaco do que o uso de um cabeçalho e acabará sendo registrado em quem sabe. Se isso suscitar preocupações de segurança para você, uma alternativa é usar um token JWT secundário apenas para o material do soquete da web .

Crie um terminal REST para gerar esse JWT , que obviamente só pode ser acessado por usuários autenticados com seu token de login principal (transmitido via cabeçalho). O JWT do soquete da Web pode ser configurado de maneira diferente do seu token de login, por exemplo, com um tempo limite mais curto, para que seja mais seguro enviar como parâmetros de consulta da sua solicitação de atualização.

Crie um JwtAuthHandler separado para a mesma rota em que o SockJS eventbusHandler é registrado . Verifique se o manipulador de autenticação está registrado primeiro, para que você possa verificar o token do soquete da Web no seu banco de dados (o JWT deve estar de alguma forma vinculado ao seu usuário no back-end).

Norbert Schöpke
fonte
Essa foi a única solução segura que eu consegui encontrar para os websockets do API Gateway. O Slack faz algo semelhante com sua API RTM e eles têm um tempo limite de 30 segundos.
21419 Andrhamm
2

Totalmente hackeado assim, graças à resposta de kanaka.

Cliente:

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

Servidor (usando o Koa2 neste exemplo, mas deve ser semelhante em qualquer lugar):

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
fonte
4
Isso não passa seu token na seção em que seu cliente deve solicitar um ou mais protocolos específicos? Posso fazer isso funcionar, também não há problema, mas decidi não fazer isso, e sim fazer o que Motes sugeriu e bloquear até que o token de autenticação seja enviado no onOpen (). Sobrecarregar o cabeçalho da solicitação de protocolo parece errado para mim e, como minha API é para consumo público, acho que vai ser meio confuso para os consumidores da minha API.
Jay
0

O meu caso:

  • Quero me conectar a um servidor WS de produção www.mycompany.com/api/ws ...
  • usando credenciais reais (um cookie de sessão) ...
  • de uma página local ( localhost:8000).

A configuração document.cookie = "sessionid=foobar;path=/"não ajudará, pois os domínios não correspondem.

A solução :

Adicionar 127.0.0.1 wsdev.company.com a /etc/hosts.

Dessa forma, seu navegador usará cookies mycompany.comquando www.mycompany.com/api/wsvocê se conectar a partir de um subdomínio válido wsdev.company.com.

Max Malysh
fonte
-1

Na minha situação (Azure Time Series Insights wss: //)

Usando o wrapper ReconnectingWebsocket e conseguiu adicionar cabeçalhos com uma solução simples:

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

Onde a carga útil neste caso é:

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

Tecnicamente, você enviará esses cabeçalhos através da função de conexão antes da fase de atualização do protocolo. Isso funcionou para mim em um nodejsprojeto:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
George
fonte
3
Isto é para o cliente websocket em npm (para nó). npmjs.com/package/websocket No geral, isso é exatamente o que estou procurando, mas no navegador.
Arnuschky 16/08
1
Foi diminuído porque esse parâmetro de cabeçalhos está na camada de protocolo WebSocket, e a pergunta é sobre cabeçalhos HTTP.
Toilal
" headersdeve ser nulo ou um objeto especificando cabeçalhos de solicitação HTTP arbitrários adicionais para enviar junto com a solicitação." de WebSocketClient.md ; portanto, headersaqui é a camada HTTP.
Momocow 28/10/19
Além disso, qualquer pessoa que queira fornecer cabeçalhos personalizados deve ter em mente a assinatura da função do connectmétodo, descrita como connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), isto é, headersdeve ser fornecida juntamente com requestOptions, por exemplo ws.connect(url, '', headers, null),. Somente a originstring pode ser ignorada neste caso.
Momocow 28/10/19
-4

Você pode passar os cabeçalhos como um valor-chave no terceiro parâmetro (opções) dentro de um objeto. Exemplo com token de autorização. Deixou o protocolo (segundo parâmetro) como nulo

ws = novo WebSocket ('ws: // localhost', nulo, {cabeçalhos: {Authorization: token}})

Editar: Parece que essa abordagem funciona apenas com a biblioteca nodejs, não com a implementação padrão do navegador. Deixando porque pode ser útil para algumas pessoas.

Nodens
fonte
Tive minhas esperanças por um segundo. Não parece haver uma sobrecarga com um terceiro parâmetro no WebSocket ctor.
Levitikon
Peguei a ideia do código wscat. github.com/websockets/wscat/blob/master/bin/wscat line 261 que usa o pacote ws. Achei que esse era um uso padrão.
Nodens 16/01