WebSocket连接更新令牌的最佳实践是什么?

32
这可能是基于个人意见的,但我仍然想知道是否有最佳实践,因为我对WebSocket实践几乎一无所知。
我有一个单页应用程序(SPA),从我的自己的OP(OpenID Provider)获取JWT令牌。然后使用该JWT连接到我拥有的其他服务,使用REST和WebSockets。
就REST而言,非常简单:
- REST API验证JWT(发送在"Authorization: Bearer..."中),提供对受保护资源的访问或响应401,让SPA知道它需要请求新的令牌。
现在考虑WebSockets:
- 在SPA加载期间,一旦获得令牌,我就会打开到我的WebService的WS。我发送的第一条消息是具有我的JWT的登录消息,然后我在服务器的websocket实例上保存此JWT以知道谁正在发送消息。 - 我接收到每个子消息时都会验证JWT是否过期。一旦过期,我理解有两个选择:
1. 使用某种“token_expired”错误丢弃websocket,并强制浏览器在刷新令牌后重新建立websocket连接。 2. 保持websocket打开,返回错误消息并发送一个新的登录消息(一旦刷新令牌)。 3. 不使用登录消息,而只是在每个请求中发送JWT。
问题:你会推荐哪种方法?在安全性和性能方面有何考虑?还有其他未列出的常见做法吗?

1
#1 OP 是什么意思? #2 经典的方法是用用户/密码交换获取新的 JWT。那么,“我发送的第一条消息是带有我的 JWT 的登录消息”是什么意思? - JRichardsz
OP 指 OpenID 提供者。当时我们尝试使用 JWT 进行 WebSocket 的身份验证,而不会打扰用户输入他的凭据,无论是通过在服务器端实现还是通过放弃 WebSocket 并使用刷新后的 JWT 进行重新授权。我将在一两天内详细分享我们想出的做法,因为对这个问题产生了如此浓厚的兴趣。虽然 REST 很容易处理,但 WS 保持套接字开启,因此需要处理仍然保持开启状态的套接字过期的情况。 - mati.o
基本流程是什么?#1 用户进入acme.com,输入其凭证,生成JWT令牌以便能够使用您的任何API,如果令牌过期,则使用WebSockets生成新的令牌?或者#2 acme.com没有登录表单,用户输入到acme.com,在使用某些逻辑的情况下,该用户使用WebSockets获取JWT令牌,如果JWT过期,则使用WebSockets生成新的令牌。......如果您的主要目标是拥有一个可以让用户使用您的任何API的网站,我可以与您分享一种干净而标准的方法,在其中您可以在某些流程部分中使用WebSockets(如果适用)。 - JRichardsz
如描述所述,用户通过使用标准的OAuth2.0流程输入其凭据来获取JWT。有了该JWT,他可以毫无问题地访问我的REST API。然而,我的某个服务通过WebSocket提供实时数据,该套接字也需要像REST一样对用户进行授权,但是,虽然REST是无状态的,但WebSocket保持打开状态,并且可能达到短暂的JWT过期时间。问题是关于这种特定情况的到期-如何更好地处理,通过关闭套接字还是通过保持其打开等待客户端使用应用程序代码进行更新。 - mati.o
您的用户能否重新登录以更新令牌? - JRichardsz
2个回答

13

我之前提出了一个比较老的问题,现在我很乐意分享我们选择的做法:

  1. 客户端第一次获取 JWT 令牌后(应用程序启动时),会打开一个 WebSocket。

  2. 为了认证通道,我们发送一条消息作为协议的一部分,称为 authMessage,其中包含该 JWT

  3. 服务器将此数据存储在套接字实例中,并在从客户端发送数据或接收数据之前验证其有效性/到期状态。

  4. 令牌在 Web 应用程序中在到期前几分钟进行静默刷新,然后向服务器发出另一条 authMessage(从第 2 步重复)。

  5. 如果由于任何原因令牌在更新之前过期,则服务器关闭该套接字。

这是我们在应用程序中实现的大致内容(未经优化),并且对我们非常有效。


如果我理解正确的话,Web应用程序刷新令牌后,您会将新信息发送到服务器。然后服务器关闭旧连接并使用新的令牌信息打开新的套接字连接? - chris
1
实际上不是这样的。它只是发送新的authMessage与更新后的JWT,并且服务器端会在sockets实例上进行更新。关闭一个socket是代价高昂的操作,因此我们希望将其最小化——仅用于刷新或过期。 - mati.o
1
但这样会允许客户端在未经身份验证的情况下建立连接,对吗?您是否建议使用令牌作为路径参数进行第一次连接/重新连接,以确保不会为未经身份验证的客户端打开连接,或者这样做是否值得? - Marian Klühspies
@MarianKlühspies 很好的观点。您绝对可以使用此技术进行初始WS连接。实际上,这比处理不会发送authMessage的挂起套接字要容易得多。一定要确保始终安全地执行此操作。有一篇关于这种方法的不错文章:https://www.linode.com/docs/guides/authenticating-over-websockets-with-jwt/ - mati.o

2
OAuth2流程有两种选项来更新令牌。正如您所说,其中一种选项是提示用户执行新的登录过程。
另一种选择是使用refresh_token,在这种情况下,您将避免向用户发出登录请求,并在会话过期时以静默方式更新令牌。
在两种情况下,您需要在客户端中存储jwt(通常在登录后),并更新它(在交互式登录或无声再生后)。 Localstorage、store或简单的全局变量都是处理存储和更新客户端中的jwt的替代方案。
正如我们所看到的,jwt再生遵循oauth2规范,并在客户端(在您的情况下为SPA)中执行。
因此,下一个问题是:我如何将此jwt(新的或更新的)传递给外部资源(经典的rest api或您的websocket)?
经典的Rest Api
在这种情况下,如您可能已知,使用http标头发送jwt很容易。在每次http调用中,我们可以作为标头发送旧/有效的jwt或更新后的jwt,通常为Authorization: Bearer ...
WebSocket
在这种情况下,它并不容易,因为根据快速审查,一旦建立了连接,没有明确的方法来更新标头或任何其他“元数据”:

此外,没有标头的概念,因此您需要使用以下内容将此信息(在您的情况下为jwt)发送到您的websocket:

  • 协议
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);
  • 饼干
document.cookie = 'MyJwt=' + jwt + ';'
var ws = new WebSocket(
    'wss://localhost:9000/wss/'
);

简单的获取参数。
var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");

仅接收文本的Websocket

根据以下链接的内容,websocket只能在稳定阶段提取标头、获取参数和协议

之后,WebSocket服务器只接收文本:
const http = require('http');
const WebSocketServer = require('websocket').server;
const server = http.createServer();
server.listen(9898);
const wsServer = new WebSocketServer({
    httpServer: server
});
wsServer.on('request', function(request) {
    const connection = request.accept(null, request.origin);
    connection.on('message', function(message) {
      //I can receive just text 
      console.log('Received Message:', message.utf8Data);
      connection.sendUTF('Hi this is WebSocket server!');
    });
    connection.on('close', function(reasonCode, description) {
        console.log('Client has disconnected.');
    });
});

在每个请求中发送JWT

经过分析之前的话题,将新令牌或更新令牌发送到WebSocket后端的唯一方法是在每个请求中发送它:

const ws = new WebSocket('ws://localhost:3210', ['json', 'xml']);
ws.addEventListener('open', () => {
  const data = {
         jwt: '2994630d-0620-47fe-8720-7287036926cd',
         message: 'Hello from the client!' 
     }
  const json = JSON.stringify(data);
  ws.send(json);
});

未涵盖的主题:

未涵盖的主题

  • 如何使用刷新令牌进行JWT再生成
  • 如何处理静默再生

如果您需要这些未涵盖的主题,请告诉我。


感谢您提供详细的答案,但是它并没有涉及到处理静默重建的主题。我会回答我们的做法。 - mati.o

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接