Websockets客户端API中的HTTP标头


200

看起来很容易使用支持此功能的任何HTTP标头客户端向您的websocket客户端添加自定义HTTP标头,但是我找不到如何使用JSON API进行操作。

但是,似乎应该在规范中支持这些标头

任何人都知道如何实现它?

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

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


14
我认为一个好的解决方案是允许WebSocket未经授权进行连接,但随后阻塞并在服务器上等待从WebSocket接收授权,该WebSocket将在其onopen事件中传输授权信息。
微粒

@Motes的建议似乎是最合适的。从onOpen进行授权调用非常容易,它允许您根据授权响应接受/拒绝套接字。我最初尝试在Sec-WebSocket-Protocol标头中发送身份验证令牌,但这听起来像是黑客。
BatteryAcid

@Motes嗨,您能解释一下“阻止并在服务器上等待”部分吗?你的意思是像不处理任何消息,直到出现“身份验证”消息?
Himal

@Himal,是的,服务器设计不得在连接开始时发送数据或接受授权以外的任何其他数据。
微粒

@Motes感谢您的回复。我对阻止部分感到有些困惑,因为据我了解您无法阻止初始connect请求。我在后端使用Django通道,并且将其设计为接受connect事件连接。然后在receive事件中设置“ is_auth”标志(如果看到有效的身份验证消息)。如果未设置is_auth标志并且不是auth消息,则它将关闭连接。
Himal

Answers:


210

更新2倍

简短答案:不,只能指定路径和协议字段。

更长的答案:

JavaScript WebSockets API中没有用于指定客户端/浏览器要发送的其他标头的方法。可以在WebSocket构造函数中指定HTTP路径(“ GET / xyz”)和协议标头(“ Sec-WebSocket-Protocol”)。

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安全信息的摘要:https : //devcenter.heroku.com/articles/websocket-security

基本身份验证以前是一个选项,但已过时,并且即使指定了标头,现代浏览器也不会发送标头。

基本身份验证信息(不建议使用)

Authorization标头是从WebSocket URI的用户名和密码(或仅用户名)字段生成的:

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

上面的结果导致以下标头带有字符串“ username:password” base64编码:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

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

感谢Dmitry Frank的基本身份验证答案


37
我遇到了同样的问题。太糟糕了,这些标准集成得太差了。您可能希望他们查看XHR API来找到WebSockets API的要求(因为WebSockets和XHR是相关的),但是似乎他们只是在一个孤岛上开发API。
eleotlecram 2012年

4
@eleotlecram,加入HyBi工作组并提出建议。该小组向公众开放,并且该协议的后续版本正在进行中。
kanaka

5
@Charlie:如果您完全控制服务器,那是一种选择。更常见的方法是从常规HTTP服务器生成票证/令牌,然后让客户端发送票证/令牌(作为Websocket路径中的查询字符串或第一个Websocket消息)。然后,websocket服务器会验证票证/令牌是否有效(尚未过期,尚未使用,来自与创建时相同的IP等)。另外,我相信大多数websockets客户端都支持基本身份验证(尽管可能对您而言还不够)。更多信息:devcenter.heroku.com/articles/websocket-security
kanaka

3
我想这是设计使然。我的印象是,该实现是有意从HTTP借用的,但应通过设计使它们尽可能地分开。规范中的文字继续:“但是,该设计并不将WebSocket限制为HTTP,并且将来的实现可以在专用端口上使用更简单的握手,而无需重新发明整个协议。这最后一点很重要,因为交互式消息传递的流量模式确实可以与标准HTTP流量不完全匹配,并且可能在某些组件上引起异常负载。”

3
不幸的是,这似乎在Edge中不起作用。谢谢,MS:/
sibbl

40

更多的替代解决方案,但是所有现代浏览器都将域cookie与连接一起发送,因此请使用:

var authToken = 'R3YKZFKBVi';

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

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

以请求连接头结尾:

Cookie: X-Authorization=R3YKZFKBVi

1
这似乎是在连接上传递访问令牌的最佳方法。在这一刻。
cammil

1
如果WS服务器URI与客户端URI不同怎么办?
丹麦

35

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

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

然后,将使用提供的username和设置适当的基本授权HTTP标头password。如果您需要基本授权,那么一切都准备就绪。


Bearer但是,我想使用,并且采取了以下技巧:我按如下所示连接到服务器:

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

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


12
我正在尝试您建议的解决方案。但是我看不到授权标头被添加到我的请求中。我已经使用其他浏览器(例如Chrome V56,Firefox V51.0)尝试过它,我在本地主机上运行服务器。因此websocket网址为“ ws:// myusername:mypassword @ localhost:8080 / mywebsocket”。知道有什么问题吗?谢谢
LearnToLive 2017年

4
通过url传输令牌是否安全?
Mergasov '17

2
使用空/忽略的用户名和非空密码作为令牌可能会更好,因为可能会记录用户名。
AndreKR

9
我同意@LearnToLive-我在wss中使用了它(例如wss://user:password@myhost.com/ws),并且Authorization在服务器端没有标头(使用Chrome版本60)
user9645

6
我有和@LearnToLive和@ user9645相同的问题;当URI为wss://user:pass@host格式时,ch​​rome和firefox均未添加授权标头。这是浏览器不支持的,还是握手有问题?
David Kaczynski '18

19

您不能添加标头,但是,如果您只需要在连接时将值传递给服务器,则可以在url上指定查询字符串部分:

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

该URL是有效的,但是-当然-您需要修改服务器代码才能对其进行解析。


14
需要谨慎使用此解决方案,查询字符串可能会被截获,登录代理等。因此,以这种方式传递敏感信息(用户/密码/身份验证令牌)将不够安全。
Nir

5
@Nir与WSS的查询字符串可能应该是安全的
Sebastien Lorber

8
ws是纯文本。使用ws协议,任何内容都可以被拦截。
加布里埃莱·卡里奥利

1
@SebastienLorber使用查询字符串是不安全的,它没有被加密,这同样适用于HTTPS,但是由于使用了“ ws:// ...”协议,所以实际上没关系。
LU4

5
@ LU4查询字符串是加密的,但也有其他原因,主机不添加敏感数据作为URL查询参数stackoverflow.com/questions/499591/are-https-urls-encrypted/...blog.httpwatch.com/2009

16

要使用JavaScript WebSockets API建立WebSockets连接时,无法发送自定义标头。您可以Subprotocols通过使用第二个WebSocket类构造函数来使用标头:

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

然后您可以使用Sec-WebSocket-Protocol服务器上的密钥获取Subprotocols标头。

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


1
Jwt可以包含逗号吗?
CESCO

1
我不相信 JWT由三个base64编码的有效载荷组成,每个有效载荷之间用句点分隔。我相信这可以排除逗号的可能性。
BillyBBone

2
我实现了它,并且效果很好-感觉很奇怪。谢谢
BatteryAcid

3
您是否建议我们使用Sec-WebSocket-Protocol标头替代Authorization标头?
rrw

13

无法发送授权标头。

附加令牌查询参数是一种选择。但是,在某些情况下,可能不希望以纯文本形式将主登录令牌作为查询参数发送,因为它比使用标头更不透明,并且最终会被记录在任何地方。如果这给您带来了安全隐患,则替代方法是仅将辅助JWT令牌用于Web套接字内容

创建一个用于生成此JWT的REST端点,当然,只有使用您的主要登录令牌(通过标头传输)进行身份验证的用户才能访问该端点。Web套接字JWT的配置可以与登录令牌不同,例如,超时时间更短,因此,将其作为升级请求的查询参数进行发送时更加安全。

为您在上注册SockJS eventbusHandler的同一路由创建一个单独的JwtAuthHandler。确保首先注册了身份验证处理程序,以便您可以针对数据库检查Web套接字令牌(JWT应该以某种方式链接到后端的用户)。


这是我可以为API Gateway Websocket想到的唯一安全的解决方案。Slack的RTM API做了类似的事情,并且有30秒的超时时间。
andrhamm '19

2

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

4
这不只是在您的客户端应请求一个或多个特定协议的部分中传递令牌吗?我可以使它正常工作,但是也没有问题,但是我决定不这样做,而是按照Motes的建议进行阻止,直到auth令牌在onOpen()上发送为止。重载协议请求标头对我来说似乎是错误的,并且因为我的API供公众使用,所以我认为这会使我的API的使用者感到困惑。
杰伊

0

我的情况:

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

document.cookie = "sessionid=foobar;path=/"由于域名不匹配,设置无济于事。

解决方案

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

这样,从有效子域进行连接mycompany.com时,浏览器将在连接时使用cookie 。www.mycompany.com/api/wswsdev.company.com


-1

在我的情况下(Azure时间序列见解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
}}}

-3

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

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

3
这适用于npm中的websocket客户端(用于节点)。npmjs.com/package/websocket总体来说,这正是我在寻找的东西,只是在浏览器中。
arnuschky

1
它被否决了,因为此标头参数位于WebSocket协议层上,而问题在于HTTP标头。
托勒勒

headers应该为null或一个对象,该对象指定要随请求一起发送的其他任意HTTP请求标头。” 来自WebSocketClient.md ; 因此,headers这里是HTTP层。
momocow

此外,任何人谁愿意提供自定义标题应该牢记的函数签名connect的方法,描述为connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]),即headers应一起提供requestOptions,例如ws.connect(url, '', headers, null)。只有origin字符串可以在这种情况下被忽略。
momocow

-4

您可以在对象内部的第三个参数(选项)中将标头作为键值传递。具有授权令牌的示例。将协议(第二个参数)留空

ws = new WebSocket('ws:// localhost',null,{标头:{授权:令牌}})

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


我的希望寄托了几秒钟。WebSocket ctor中似乎没有重载第3个参数。
Levitikon

从wscat代码获得了这个想法。github.com/websockets/wscat/blob/master/bin/wscat第261行使用ws软件包。以为这是标准用法。
Nodens
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.