如何在 JWT 中使用 jti 声明

How to use jti claim in a JWT

JWT spec 提到了一个 jti 声明,据称它可以用作防止重放攻击的随机数:

The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. Use of this claim is OPTIONAL.

我的问题是,我将如何实施它?我是否需要存储以前使用的 jtis 并为每个请求发出一个新的 JWT?如果是这样,这不是违背了 JWT 的目的吗?为什么要使用 JWT 而不是仅将随机生成的会话 ID 存储在数据库中?

我的 REST API 有一个 Mongo 数据库,我不反对添加 Redis 实例。有没有比 JWT 更好的身份验证选项?我主要只是不想在客户端上存储密码,这消除了 HTTP 身份验证作为一种选择,但是,随着我越来越深入地了解这个 JWT 的东西,我开始觉得好像自定义令牌实现或不同的标准可能更适合我的需要。是否有任何 node/express 支持令牌撤销和旋转令牌的基于令牌的身份验证包?

如有任何建议,我们将不胜感激。

事实上,存储所有发布的 JWT ID 破坏了使用 JWT 的无状态性质。但是,JWT ID 的目的是能够撤销以前发布的 JWT。这可以通过黑名单而不是白名单最容易地实现。如果您已经包含 "exp" 声明(您应该),那么您最终可以在自然过期时清理列入黑名单的 JWT。当然,您可以同时实施其他撤销选项(例如,根据 "iat" 和 "aud" 的组合撤销一个客户端的所有令牌)。

你可以使用 express-jwt 包

参见 GitHub or on NPM 上的 express-jwt。

Express-jwt 处理撤销的令牌,如下所述:https://github.com/auth0/express-jwt#revoked-tokens

var jwt = require('express-jwt');
var data = require('./data');
var utilities = require('./utilities');

var isRevokedCallback = function(req, payload, done){
  var issuer = payload.iss;
  var tokenId = payload.jti;

  data.getRevokedToken(issuer, tokenId, function(err, token){
    if (err) { return done(err); }
    return done(null, !!token);
  });
};

app.get('/protected',
  jwt({secret: shhhhhhared-secret,
    isRevoked: isRevokedCallback}),
  function(req, res) {
    if (!req.user.admin) return res.send(401);
    res.send(200);
  });

您还可以阅读第 4 部分。我们如何避免增加开销? 来自 this oauth0 blog post

这是一个老问题,但我刚刚研究过类似的问题。所以我会在这里分享我的想法。

首先,我同意在验证 JWT 令牌时进行数据库调用会破坏其无状态的主要优势。

None 之前的答案提到了刷新令牌,但我相信它们在可伸缩性和安全性之间提供了很好的 trade-off。

简而言之,可以使用到期时间较短(比如 15 分钟)的常规身份验证令牌,并使用 long-lived 访问权限(比如 2 周)刷新令牌。每当授权令牌过期时,刷新令牌(存储得更安全)用于生成新的授权令牌,而无需用户再次登录。

jti 声明最适合刷新令牌。这使您能够撤销访问权限,同时最大限度地减少数据库调用次数。

假设平均用户会话为 30 分钟。如果您对常规身份验证令牌有 jti 声明,那么每个 API 调用都会执行至少一次额外的数据库调用,以检查该令牌是否未列入黑名单。但是,如果您仅对刷新令牌使用 jti 声明,则在 30 分钟的会话过程中,您将仅进行 2 次数据库调用以进行身份​​验证(假设每个身份验证令牌在 15 分钟后过期)。这是一个很大的不同。

关于实现,您可以使用 randomly-generated UID 并将其用作 table 的主键。这保证了调用尽可能快。此外,您可以添加与 exp 声明具有相同值的 expiration_time 列。这样您就可以轻松(批量)删除所有过期的刷新令牌。

Why use a JWT instead of just storing a randomly-generated session ID in a database?

或者,如果我可以解释一下,“为什么要使用 JWT 刷新令牌而不是保存在数据库中的随机字符串?”

我认为你可以做到这一点,但使用 JWT 令牌至少有两个优点:(1) 如果令牌无效或过期(当你解码它时),你不必进行任何数据库调用根本。您只是 return 一个带有错误状态代码的响应。 (2) 如果你有一个大系统,你可能会根据某些标准(比如,每个客户端应用程序 [web vs mobile])将“随机字符串”存储在不同的数据库 table 中。您如何知道要在其中查找随机字符串的 table?使用 JWT 令牌,您可以简单地添加一个 client_id 声明。因此,在令牌中包含信息的能力很有用。

虽然在大多数情况下 JWT 令牌应该离线验证,但在某些情况下在线验证可能更有意义。您的应用程序几乎肯定会公开多种类型的操作,其中一些操作比其他操作风险更大(并且可能不太频繁)。这些冒险行为有时可能会受益于在线令牌验证,因为当令牌被验证时,可能不再安全地使用它(因为它可能已被撤销,例如,这对于 longer-lived 尤为明显) JWT——这反过来又最大限度地减少了刷新压力)。另一件事是你可能有某种风险引擎解决方案,它可以决定发行代币是否是一个好的决定。