Websockets客户端API中的HTTP标头

Jul*_*oux 170 javascript header http websocket

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

然而,似乎应该在规范中支持这些标题.

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

var ws = new WebSocket("ws://example.com/service");
Run Code Online (Sandbox Code Playgroud)

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

kan*_*aka 181

更新2x

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

更长的回答:

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"]);
Run Code Online (Sandbox Code Playgroud)

以上结果如下:

Sec-WebSocket-Protocol: protocol
Run Code Online (Sandbox Code Playgroud)

Sec-WebSocket-Protocol: protocol1, protocol2
Run Code Online (Sandbox Code Playgroud)

实现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")
Run Code Online (Sandbox Code Playgroud)

以上结果在以下标题中使用字符串"username:password"base64 encoded:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Run Code Online (Sandbox Code Playgroud)

我已经在Chrome 55和Firefox 50中测试了基本身份验证,并验证了基本身份验证信息确实与服务器协商(这可能在Safari中无效).

感谢Dmitry Frank的基本认可答案

  • 我遇到了同样的问题.太糟糕了,这些标准很难整合.您希望他们查看XHR API以查找WebSockets API的要求(因为WebSockets和XHR是相关的),但似乎他们只是在岛上开发API本身. (32认同)
  • @Charlie:如果你完全控制服务器,这是一个选择.更常见的方法是从普通HTTP服务器生成票证/令牌,然后让客户端发送票证/令牌(作为websocket路径中的查询字符串或作为第一个websocket消息).然后,websocket服务器验证票证/令牌是否有效(尚未过期,尚未使用,来自与创建时相同的IP等).此外,我相信大多数websockets客户端都支持基本身份验证(尽管可能还不够).更多信息:https://devcenter.heroku.com/articles/websocket-security (5认同)
  • @eleotlecram,加入HyBi工作组并提出建议.该小组向公众开放,并且该协议的后续版本正在进行中. (4认同)
  • 我想它的设计.我的印象是,实现是故意从HTTP借用,但是尽可能通过设计将它们分开.具体说明中的文字继续说:"但是,设计不会将WebSocket限制为HTTP,未来的实现可以在专用端口上使用更简单的握手,而无需重新构建整个协议.最后一点非常重要,因为交互式消息传递的流量模式很重要不能与标准HTTP流量紧密匹配,并可能导致某些组件出现异常负载." (3认同)
  • 不幸的是,这似乎不适用于Edge.谢谢,MS:/ (3认同)
  • @shabunc 2019,情况仍然如此:( (2认同)
  • 我正在解决这些限制并利用协议字段作为身份验证字段。与使用“身份验证:不记名令牌”没有什么不同...只是在允许 WS 打开之前观察不同的标头...如果有人需要有关此方法的详细信息,我可以将我的解决方案粘贴为新答案 (2认同)

kaq*_*qao 52

以下是对 2024 年以及之后很长一段时间内遇到这种情况的疲惫旅行者的情况的快速总结。

自从这个问题提出以来,12(!)年里什么都没有改变。JavaScript WebSocket API 已被所有浏览器供应商放弃(尽管实现偶尔会得到更新),并且新规范(WebSocket StreamWebTransport)还远未实现。这一切意味着 WebSocket 仍然被广泛使用,尽管7 年前被称为“遗留”,但不存在损坏的 API 的替代品,但不存在损坏的 API 的替代品,并且问题中概述的问题与以前一样令人烦恼,甚至更多。

处理这种情况的选项(剧透,#5 或#6 就是你想要的):

1. 外部实现认证

https://devcenter.heroku.com/articles/websocket-security中进行了描述。客户端应向专用端点发出经过身份验证的请求,该端点将生成并保留一个短期令牌,该令牌也将发送给客户端。然后,客户端在打开 WebSocket 时返回此令牌作为 URL 参数。服务器可以验证它并接受/拒绝协议升级。这就需要服务器专门针对WebSockets实现一个完全自定义的、有状态的认证机制,这在很多场景下是一座太过遥远的桥梁。

2.通过WebSocket发送认证信息

您在未进行身份验证的情况下打开 WebSocket,然后在执行其他操作之前通过 WebSocket 发送您的身份验证信息。从理论上讲,这听起来很合乎逻辑(并且是由浏览器供应商建议的),但如果只是粗略地思考一下,就会分崩离析。服务器旨在实现一种笨拙的、高度有状态的、完全自定义的身份验证机制,该机制与其他任何东西都不能很好地配合,除了必须与拒绝身份验证的客户端保持持久连接之外,还为拒绝身份验证的客户端敞开了大门。拒绝服务攻击,或者进入一个全新的兔子整体,强制执行严格的超时以防止恶意行为。

3. 通过 URL 参数发送身份验证信息(例如访问令牌)

并不像听起来那么可怕,只要强制执行 SSL(wss://不是ws://),因为 WebSocket URL 很特殊,不会保存在浏览器历史记录或类似内容中。最重要的是,访问令牌通常是短暂的,因此也减轻了危险。但。无论如何,服务器很可能会在某个时刻记录该 URL。即使您的服务器应用程序不这样做,框架或(云)主机也可能会这样做。此外,如果您必须传递 ID 令牌(就像 Firebase 习惯做的那样),那么随着 ID 令牌变得巨大,您可能会遇到各种 URL 长度限制。

4. 通过一个好的旧 cookie 进行身份验证

不。WebSocket 不受同源策略的约束(因为显然 WebSocket 的每一个小细节都非常糟糕),并且允许 cookie 会让您很容易受到 CSRF 攻击。使用 CSRF 令牌修复此问题的描述如下,但它比采用此列表中的任何其他方法更困难,因此根本不值得考虑。

5. 在内部走私访问令牌Sec-WebSocket-Protocol

由于浏览器允许您控制的唯一标头是Sec-WebSocket-Protocol,因此您可以滥用它来模拟任何其他标头。有趣(或者更滑稽的是),这就是Kubernetes 正在做的事情。简而言之,您可以将身份验证所需的任何内容附加为内部额外支持的子协议Sec-WebSocket-Protocol

var ws = new WebSocket("ws://example.com/path", ["realProtocol", "yourAccessTokenOrSimilar"]);
Run Code Online (Sandbox Code Playgroud)

然后,在服务器上,您添加某种中间件,在将请求进一步传递到系统之前将其转换回更清晰的形式。是的,很糟糕,但迄今为止最好的解决方案。URL 中没有令牌,没有为小型中间件保存的自定义身份验证,服务器上不需要额外的状态。不要忘记包含真正的子协议,因为各种工具将拒绝没有子协议的连接。

6. 切换到 SSE(或 RSocket?)(如果适用)

对于很多情况,SSE 可能是一个不错的替代品。浏览器EventSourceAPI 严重损坏WebSocket(除了 GET 请求之外它无法发送任何内容,尽管是常规 HTTP,但也无法发送标头),但是!它可以很容易地被替换为fetch一个更健全的 API。这种方法可以很好地替代 WebSocket,例如在 GraphQL 订阅中,或者在不强制要求全双工的任何地方。这可能涵盖了大多数场景。RSocket理论上也可以是一个选项,但看看它是如何通过浏览器中的 WebSocket 实现的,我认为它实际上并不能解决任何问题,但我没有对其进行足够深入的研究,无法绝对肯定地说。

  • 谢谢您的回答!它为我在 2023 年节省了大量研究! (4认同)

Dmi*_*ank 32

可以使用以下方法解决HTTP授权标头问题:

var ws = new WebSocket("ws://username:password@example.com/service");
Run Code Online (Sandbox Code Playgroud)

然后,将使用提供的username和设置适当的基本授权HTTP标头password.如果您需要基本授权,那么您已经完成了所有设置.


我想使用Bearer,然后我采用了以下技巧:我按如下方式连接到服务器:

var ws = new WebSocket("ws://my_token@example.com/service");
Run Code Online (Sandbox Code Playgroud)

当我的服务器端代码收到带有非空用户名和空密码的Basic Authorization标头时,它会将用户名解释为令牌.

  • 我正在尝试你提出的解决方案.但我无法看到Authorization标头被添加到我的请求中.我尝试使用不同的浏览器,例如Chrome V56,Firefox V51.0我在我的localhost上运行我的服务器.所以websocket url是"ws:// myusername:mypassword @ localhost:8080/mywebsocket".知道什么可能是错的吗?谢谢 (12认同)
  • 这些网址`http://username:password@example.com` 的使用已被弃用。https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication。也许这就是它不适用于 websockets 的原因! (9认同)
  • 我同意@LearnToLive - 我使用wss(例如`wss:// user:password @ myhost.com/ws`)并且在服务器端没有"Authorization"标题(使用Chrome版本60) (8认同)
  • 我和@LearnToLive和@ user9645有同样的问题; 当URI采用`wss:// user:pass @ host`格式时,chrome和firefox都不会添加授权标头.这是不是浏览器支持,还是握手出了问题? (5认同)
  • 通过URL传输令牌是否安全? (3认同)
  • 清空/忽略用户名和非空密码作为令牌可能会更好,因为用户名可能会被记录. (2认同)

Tim*_*Tim 25

更多的替代解决方案,但所有现代浏览器都会发送域cookie以及连接,因此使用:

var authToken = 'R3YKZFKBVi';

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

var ws = new WebSocket(
    'wss://localhost:9000/wss/'
);
Run Code Online (Sandbox Code Playgroud)

最后得到请求连接标头:

Cookie: X-Authorization=R3YKZFKBVi
Run Code Online (Sandbox Code Playgroud)

  • @Danish那么这不起作用,因为你不能为其他域客户端设置cookie (8认同)
  • 如果 WS 服务器 URI 与客户端 URI 不同怎么办? (7认同)
  • Websocket 请求不受同源策略约束。以 cookie 形式发送身份验证将使您的应用程序容易被劫持。请参阅 https://christian-schneider.net/CrossSiteWebSocketHijacking.html (5认同)
  • 但是,您可以设置一个 HTTP 服务,在相关路径上设置会话 cookie,并在启动 websocket 之前调用它。比如说,调用 `https://example.com/login`,并让响应在 `/wss` 上设置 cookie,然后 `new WebSocket("wss://example.com/wss")` 将开始握手使用相关 cookie 进行请求。请注意,开发工具可能不会显示 cookie,但仍应发送它。 (4认同)
  • 这似乎确实是在连接上传递访问令牌的最佳方式。眼下。 (2认同)
  • Christian Schneider(上述文章的作者)建议在使用身份验证 Cookie 时使用 CSRF-Tokens 来保护初始 HTTP 握手: `ws = new WebSocket("wss://example.com/wss?csrftoken=<token>") ` (2认同)
  • 您也可以简单地检查来源。 (2认同)
  • @Coderer 的低估评论:“请注意,开发工具可能不会显示 cookie,但仍应发送它。”我已经确认开发工具不会在 websocket 请求/连接上显示这些 cookie!令人烦恼的是,这导致我浪费了很多时间来思考我做错了什么。 (2认同)

Pet*_*ndi 24

对于那些在 2021 年仍在苦苦挣扎的人来说,Node JS 全局 Web 套接字类options在构造函数中有一个附加字段。如果你去查看WebSockets类的实现,你会发现这个变量声明。您可以看到它接受三个 params 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;
};
Run Code Online (Sandbox Code Playgroud)

如果您使用的是 Node Js 库,例如react、react-native。这是一个如何做到这一点的示例。

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

请注意我已通过 null 的协议。如果您使用的是 jwt,则可以使用Bearer+传递 Authorization 标头token

免责声明,并非所有浏览器都支持此功能,从 MDN Web 文档中您可以看到仅记录了两个参数。请参阅 https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#syntax


Gab*_*oli 18

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

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

该URL有效,但 - 当然 - 您需要修改服务器代码以解析它.

  • 需要小心这个解决方案,查询字符串可能被截获,登录代理等等,因此以这种方式传递敏感信息(用户/密码/身份验证令牌)将不够安全. (9认同)
  • ws是纯文本.使用ws协议可以拦截任何东西. (7认同)
  • @Nir与WSS的查询字符串应该是安全的 (5认同)
  • @ Lu4查询字符串已加密,但还有许多其他原因不能将敏感数据添加为URL查询参数/sf/ask/34971401/#499594&https ://blog.httpwatch.com/2009/02/20/how-secure-are-query-strings-over-https/ as refs (3认同)

Sae*_*fam 12

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

var ws = new WebSocket("ws://example.com/service", "soap");
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我实现了这个并且它有效 - 只是感觉很奇怪.谢谢 (2认同)
  • 您是否建议我们使用“ Sec-WebSocket-Protocol”标头替代“ Authorization”标头? (2认同)

小智 9

无法发送授权标头。

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

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

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

  • 请注意,如果您从 Node-js(而不是浏览器)创建 WebSocket 连接,则可以发送授权标头。请参阅此处:https://github.com/apollographql/apollo-client/issues/3967#issuecomment-882338049 (2认同)

Rya*_*iss 6

感谢卡纳卡的回答,完全像这样破解了它。

客户:

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

服务器(在本例中使用 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...
Run Code Online (Sandbox Code Playgroud)

  • 这不是只是在您的客户端应该请求一个或多个特定协议的部分传递您的令牌吗?我可以让它工作,也没有问题,但我决定不这样做,而是按照 Motes 的建议进行操作并阻止,直到在 onOpen() 上发送身份验证令牌。重载协议请求标头对我来说似乎是错误的,并且由于我的 API 是供公共使用的,我认为这会让我的 API 的消费者感到有点困惑。 (7认同)

Bic*_*ind 5

在我的情况下(Azure 时序见解 wss://)

使用 ReconnectingWebsocket 包装器并能够通过简单的解决方案实现添加标头:

socket.onopen = function(e) {
    socket.send(payload);
};
Run Code Online (Sandbox Code Playgroud)

本例中的有效负载是:

{
  "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
}}}
Run Code Online (Sandbox Code Playgroud)