Websockets客户端API中的HTTP报头

看起来很容易添加自定义HTTP头到你的websocket客户端与任何HTTP头客户端支持这一点,但我找不到如何与web平台的WebSocket API。

有人知道怎么做到吗?

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

具体来说,我需要能够发送HTTP授权标头。

369688 次浏览

更新2 x

简单回答:不可以,只能指定path和protocol字段。

再答:

JavaScript websocket API中没有方法指定客户端/浏览器要发送的附加头信息。HTTP路径(“GET /xyz"”)和协议头(“Sec-WebSocket-Protocol"”)可以在WebSocket构造函数中指定。

Sec-WebSocket-Protocol报头(有时会扩展到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安全信息的总结

基本身份验证以前是一个选项,但这已经被弃用,现代浏览器即使指定了头信息也不会发送。

基本认证信息(已弃用-不再功能):

注意:以下信息在任何现代浏览器中都不再准确。

授权头是由WebSocket URI的用户名和密码(或者只是用户名)字段生成的:

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

上面的结果在下面的头文件中包含字符串"username:password"base64编码:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

我已经在Chrome 55和Firefox 50中测试了基本身份验证,并验证了基本身份验证信息确实是与服务器协商的(这可能在Safari中不起作用)。

感谢德米特里·弗兰克的基本认证回答

当你想使用JavaScript WebSockets API建立WebSockets连接时,你不能发送自定义头。 你可以使用第二个WebSocket类构造函数来使用Subprotocols头文件:

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

然后你可以在服务器上使用Sec-WebSocket-Protocol键获取Subprotocols头。

还有一个限制,你的Subprotocols头值不能包含逗号(,) !

HTTP授权头问题可以通过以下方式解决:

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

然后,将使用提供的usernamepassword设置适当的基本授权HTTP报头。如果您需要基本授权,那么您就万事俱备了。


我想使用Bearer,但是,我诉诸于以下技巧:我连接到服务器如下:

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

当我的代码在服务器端接收到基本授权头非空用户名和空密码时,它将用户名解释为令牌。

从技术上讲,您将在协议升级阶段之前通过connect函数发送这些头文件。这在一个nodejs项目中为我工作:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);

你不能添加头,但是,如果你只需要在连接的时候向服务器传递值,你可以在url上指定一个查询字符串部分:

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

该URL是有效的,但当然,您需要修改服务器代码来解析它。

多亏了kanaka的回答。

客户:

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...

更多的替代解决方案,但所有现代浏览器发送域cookie随着连接,所以使用:

var authToken = 'R3YKZFKBVi';


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


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

以请求连接头结束:

Cookie: X-Authorization=R3YKZFKBVi

不可能发送授权头。

附加令牌查询参数是一个选项。然而,在某些情况下,以纯文本作为查询参数发送主登录令牌可能是不可取的,因为它比使用报头更不透明,并且最终会被记录在任何地方。如果这引起了您的安全问题,另一种选择是使用第二个JWT令牌来处理web套接字

为生成这个JWT创建一个REST端点,当然只有通过主登录令牌认证的用户才能访问它(通过报头传输)。web套接字JWT可以配置不同于您的登录令牌,例如有一个更短的超时,所以它是更安全的发送作为您的升级请求的查询参数。

为注册SockJS eventbusHandler的同一路由创建一个单独的JwtAuthHandler。确保先注册了认证处理程序,这样你就可以根据数据库检查web套接字令牌(JWT应该在后端以某种方式链接到你的用户)。

你可以在对象内部以第三个参数中的键值 (options)的形式传递头文件。 使用授权令牌的示例。保留协议(第二个参数)为null



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


编辑:这种方法似乎只适用于nodejs库,不适用于标准的浏览器实现。因为它可能对某些人有用。

我的情况:

  • 我想连接到一个生产WS服务器www.mycompany.com/api/ws
  • 使用真实凭证(会话cookie)…
  • 从本地页面(localhost:8000)。

设置document.cookie = "sessionid=foobar;path=/"没有帮助,因为域不匹配。

解决方案:

127.0.0.1 wsdev.company.com添加到/etc/hosts

这样,当你从有效的子域wsdev.company.com连接到www.mycompany.com/api/ws时,你的浏览器将使用来自mycompany.com的cookie。

在我的情况下(Azure Time Series Insights 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
}}}

所有未来调试器-直到今天,即15-07-21

浏览器也不支持向服务器发送客户标头,因此任何此类代码

    import * as sock from 'websocket'
const headers = {
Authorization: "bearer " + token
};
console.log(headers);


const wsclient = new sock.w3cwebsocket(
'wss://' + 'myserver.com' + '/api/ws',
'',
'',
headers,
null
);

这在浏览器中是行不通的。这背后的原因是浏览器原生Websocket构造函数不接受头文件。

正如我上面所示,w3cwebsocket承包商接受头文件,因此很容易被误导。然而,这在node.js中是有效的。

对于那些在2021年还在苦苦挣扎的人来说,Node JS全局web套接字类在构造函数中有一个额外的options字段。如果你去WebSockets类的实现,你会发现这个变量声明。你可以看到它接受三个参数url,这是必需的,protocols(可选),这是一个字符串,一个字符串数组或null。然后是第三个参数options。我们的兴趣,一个对象和(仍然可选)。看到……

declare var WebSocket: {
prototype: WebSocket;
new (
uri: string,
protocols?: string | string[] | null,
options?: {
headers: { [headerName: string]: string };
[optionName: string]: any;
} | null,
): WebSocket;
readonly CLOSED: number;
readonly CLOSING: number;
readonly CONNECTING: number;
readonly OPEN: number;
};

如果你使用的是Node Js库,比如react,请使用react-native。这里有一个例子,你可以这样做。

 const ws = new WebSocket(WEB_SOCKETS_URL, null, {
headers: {
['Set-Cookie']: cookie,
},
});

注意,对于协议,我传递了null。如果你正在使用jwt,你可以通过Bearer + token传递授权头

免责声明,这可能不是所有的浏览器都支持,从MDN web文档中,你可以看到只有两个参数被记录。 看到https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#syntax < / p >

我发现最好的方法是将jwt像常规消息一样发送到服务器。让服务器监听此消息并在此时进行验证。如果有效,则将其添加到存储的连接列表中。否则,返回一条消息,说该连接无效并关闭连接。下面是客户端代码。后台是一个使用Websockets的nestjs服务器。

  socket.send(
JSON.stringify({
event: 'auth',
data: jwt
})
);

推荐的方法是通过URL查询参数

// authorization: Basic abc123
// content-type: application/json
let ws = new WebSocket(
"ws://example.com/service?authorization=basic%20abc123&content-type=application%2Fjson"
);

这被认为是一个安全的最佳实践,因为: