【问题标题】:HTTP headers in Websockets client APIWebsockets 客户端 API 中的 HTTP 标头
【发布时间】:2011-05-20 15:46:30
【问题描述】:

看起来很容易使用任何支持此功能的 HTTP 标头客户端将自定义 HTTP 标头添加到您的 websocket 客户端,但我找不到如何使用 JSON API 来做到这一点。

不过,好像应该支持these headers in the spec

有人知道如何实现它吗?

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

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

【问题讨论】:

  • 我认为一个好的解决方案是允许 WebSocket 在未经授权的情况下连接,然后阻塞并等待服务器从 webSocket 接收授权,该 webSocket 将在其 onopen 事件中传输授权信息。
  • @Motes 的建议似乎是最合适的。从 onOpen 进行授权调用非常容易,它允许您根据授权响应接受/拒绝套接字。我最初尝试在 Sec-WebSocket-Protocol 标头中发送身份验证令牌,但感觉就像是 hack。
  • @Motes 嗨,你能解释一下“阻止并在服务器上等待”部分吗?你的意思是在有“auth”消息之前不要处理任何消息?
  • @Himal,是的,服务器设计在连接开始时不得发送数据或接受除授权之外的任何其他数据。
  • @Motes 感谢您的回复。我对阻塞部分有点困惑,因为据我了解,您无法阻止最初的 connect 请求。我在后端使用 Django 通道,并且我将其设计为接受 connect 事件上的连接。然后它在receive 事件中设置一个“is_auth”标志(如果它看到有效的身份验证消息)。如果未设置 is_auth 标志并且它不是身份验证消息,则它会关闭连接。

标签: javascript http header websocket


【解决方案1】:

对于那些在 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 传递 Authorization 标头

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

【讨论】:

    【解决方案2】:

    致所有未来的调试器 - 直到今天,即 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 构造函数不接受 headers。

    您很容易被误导,因为 w3cwebsocket 承包商接受我上面显示的标头。然而,这在 node.js 中有效。

    【讨论】:

      【解决方案3】:

      更新了 2 倍

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

      更长的答案:

      JavaScript WebSockets 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 安全信息摘要: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 的基本身份验证 answer

      【讨论】:

      • 我遇到了同样的问题。太糟糕了,这些标准的集成度太差了。您可能希望他们查看 XHR API 以找到 WebSockets API 的需求(因为 WebSockets 和 XHR 是相关的),但似乎他们只是在一个孤岛上单独开发 API。
      • @eleotlecram,加入 HyBi 工作组并提出建议。该小组向公众开放,并且正在为协议的后续版本进行工作。
      • @Charlie:如果您完全控制服务器,这是一种选择。更常见的方法是从您的普通 HTTP 服务器生成票证/令牌,然后让客户端发送票证/令牌(作为 websocket 路径中的查询字符串或作为第一个 websocket 消息)。然后,websocket 服务器验证票证/令牌是否有效(尚未过期、尚未使用、来自与创建时相同的 IP 等)。另外,我相信大多数 websockets 客户端都支持基本身份验证(但对你来说可能还不够)。更多信息:devcenter.heroku.com/articles/websocket-security
      • 我猜它是设计使然。我的印象是,该实现是故意从 HTTP 借用的,但在设计上尽可能地将它们分开。规范中的文字继续说:“然而,设计并没有将 WebSocket 限制为 HTTP,未来的实现可以在专用端口上使用更简单的握手,而无需重新发明整个协议。最后一点很重要,因为交互式消息传递的流量模式确实与标准 HTTP 流量不匹配,可能会导致某些组件出现异常负载。”
      • 不幸的是,这似乎在 Edge 中不起作用。谢谢,女士:/
      【解决方案4】:

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

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

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

      【讨论】:

      • 我的希望高了几秒钟。在 WebSocket ctor 中似乎没有采用第三个参数的重载。
      • 从 wscat 代码中得到这个想法。 github.com/websockets/wscat/blob/master/bin/wscat 第 261 行使用 ws 包。认为这是标准用法。
      • 这对客户端不起作用,在服务器上这个答案似乎无关紧要。
      【解决方案5】:

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

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

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

      这里的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
      }}}
      

      【讨论】:

      • 这看起来不像是传递自定义标头的通用方式,因为在他们的API 中看不到选项。它是通用方式还是仅适用于 Azure 和某些服务器端配置?
      【解决方案6】:

      我的情况:

      • 我想通过www.mycompany.com/api/ws 连接到生产 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。

      【讨论】:

        【解决方案7】:

        从技术上讲,您将在协议升级阶段之前通过连接函数发送这些标头。这在nodejs 项目中对我有用:

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

        【讨论】:

        • 这是用于 npm 中的 websocket 客户端(用于节点)。 npmjs.com/package/websocket 总的来说,这正是我正在寻找的,但在浏览器中。
        • 它被否决了,因为这个 headers 参数在 WebSocket 协议层,问题是关于 HTTP 标头。
        • "headers 应该为 null 或指定附加任意 HTTP 请求标头与请求一起发送的对象。"来自WebSocketClient.md;因此,这里的headers 是HTTP 层。
        • 另外,任何想要提供自定义标头的人都应该记住connect方法的函数签名,描述为connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]),即headers应该与requestOptions一起提供,例如ws.connect(url, '', headers, null)。在这种情况下,只能忽略 origin 字符串。
        • 此解决方案仅适用于ws nodejs 客户端
        【解决方案8】:

        无法发送授权标头。

        附加令牌查询参数是一个选项。但是,在某些情况下,可能不希望以纯文本形式发送主登录令牌作为查询参数,因为它比使用标头更不透明,并且最终会被记录在不知何处。如果这引起了您的安全问题,另一种方法是仅将辅助 JWT 令牌用于 Web 套接字内容

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

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

        【讨论】:

        • 这是我能为 API Gateway websockets 想出的唯一安全解决方案。 Slack 用他们的 RTM API 做了类似的事情,他们有 30 秒的超时。
        • 请注意,如果您从 node-js(而不是浏览器)创建 websocket 连接,则可以发送授权标头。见这里:github.com/apollographql/apollo-client/issues/…
        【解决方案9】:

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

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

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

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

        【讨论】:

        • Jwt 可以包含逗号吗?
        • 我不这么认为。 JWT 由三个 base64 编码的有效负载组成,每个有效负载由句点分隔。我相信这排除了逗号的可能性。
        • 我实现了这个并且它有效 - 只是感觉很奇怪。谢谢
        • 您是否建议我们使用Sec-WebSocket-Protocol 标头替代Authorization 标头?
        【解决方案10】:

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

        var authToken = 'R3YKZFKBVi';
        
        document.cookie = 'X-Authorization=' + authToken + '; path=/';
        
        var ws = new WebSocket(
            'wss://localhost:9000/wss/'
        );
        

        以请求连接标头结束:

        Cookie: X-Authorization=R3YKZFKBVi
        

        【讨论】:

        • 如果 WS 服务器 URI 与客户端 URI 不同怎么办?
        • @Danish 那行不通,因为您不能为其他域客户端设置 cookie
        • 但是,您可以设置一个 HTTP 服务,在相关路径上设置会话 cookie,并在启动 websocket 之前调用它。例如,调用https://example.com/login,并让响应在/wss 上设置一个cookie,然后new WebSocket("wss://example.com/wss") 将使用相关cookie 开始其握手请求。请注意,开发工具可能不会显示 cookie,但仍应发送。
        • Websocket 请求不受同源策略的约束。将身份验证作为 cookie 发送将使您的应用程序容易被劫持。见christian-schneider.net/CrossSiteWebSocketHijacking.html
        • Christian Schneider(上篇文章的作者)建议在使用 Authentication Cookie 时使用 CSRF-Tokens 来保护初始 HTTP 握手:ws = new WebSocket("wss://example.com/wss?csrftoken=<token>")
        【解决方案11】:

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

        【讨论】:

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

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

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

        那个 URL 是有效的,但是 - 当然 - 你需要修改你的服务器代码来解析它。

        【讨论】:

        • 需要小心使用此解决方案,查询字符串可能会被截获、登录代理等,因此以这种方式传递敏感信息(用户/密码/身份验证令牌)将不够安全。
        • @Nir 与 WSS 查询字符串应该是安全的
        • ws 是纯文本。使用 ws 协议可以拦截任何东西。
        • @SebastienLorber 使用查询字符串是不安全的,它没有被加密同样适用于 HTTPS,但是由于使用了“ws://...”协议,这真的没关系.
        • @Lu4 查询字符串已加密,但还有许多其他原因不将敏感数据添加为 URL 查询参数 stackoverflow.com/questions/499591/are-https-urls-encrypted/…blog.httpwatch.com/2009/02/20/… 作为参考
        【解决方案13】:

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

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

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


        我想使用Bearer,但我使用了以下技巧:我连接到服务器如下:

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

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

        【讨论】:

        • 我正在尝试您建议的解决方案。但是我看不到 Authorization 标头被添加到我的请求中。我已经尝试过使用不同的浏览器,例如Chrome V56、Firefox V51.0 我在本地主机上运行我的服务器。所以 websocket url 是“ws://myusername:mypassword@localhost:8080/mywebsocket”。知道可能出了什么问题吗?谢谢
        • 通过url传输token安全吗?
        • 我同意 @LearnToLive - 我将它与 wss 一起使用(例如 wss://user:password@myhost.com/ws)并且在服务器端没有 Authorization 标头(使用 Chrome 版本 60)
        • 我和@LearnToLive 和@user9645 有同样的问题;当 URI 为 wss://user:pass@host 格式时,chrome 和 firefox 都不会添加授权标头。这是浏览器不支持,还是握手有问题?
        • 这些网址http://username:password@example.com 的使用已被弃用。 developer.mozilla.org/en-US/docs/Web/HTTP/Authentication。也许这就是它也不适用于 websockets 的原因!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-19
        • 1970-01-01
        • 2017-03-20
        • 2011-10-19
        • 2023-03-25
        • 2021-12-05
        相关资源
        最近更新 更多