553

对于我正在处理的新 node.js 项目,我正在考虑从基于 cookie 的会话方法切换(我的意思是,将 id 存储到包含用户浏览器中的用户会话的键值存储)到使用 JSON Web 令牌 (jwt) 的基于令牌的会话方法(无键值存储)。

该项目是一个使用 socket.io 的游戏 - 在单个会话中存在多个通信通道(web 和 socket.io)的情况下,具有基于令牌的会话将很有用

如何使用 jwt 方法从服务器提供令牌/会话失效?

我还想了解这种范式应该注意哪些常见(或不常见)的陷阱/攻击。例如,如果这种范式容易受到与基于会话存储/cookie 的方法相同/不同类型的攻击。

所以,假设我有以下内容(改编自thisthis):

会话存储登录:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

基于令牌的登录:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

--

会话存储方法的注销(或无效)需要使用指定令牌更新 KeyValueStore 数据库。

在基于令牌的方法中似乎不存在这种机制,因为令牌本身将包含通常存在于键值存储中的信息。

4

32 回答 32

524

我也一直在研究这个问题,虽然下面的想法都不是完整的解决方案,但它们可能会帮助其他人排除想法,或者提供更多的想法。

1)只需从客户端删除令牌

显然这对服务器端的安全没有任何帮助,但它确实通过从存在中删除令牌来阻止攻击者(即,他们必须在注销之前窃取令牌)。

2)创建令牌阻止列表

您可以将无效令牌存储到其初始到期日期,并将它们与传入请求进行比较。这似乎否定了首先完全基于令牌的原因,因为您需要为每个请求触摸数据库。不过,存储大小可能会更低,因为您只需要存储在注销和到期时间之间的令牌(这是一种直觉,并且绝对取决于上下文)。

3) 只需缩短令牌到期时间并经常轮换它们

如果您将令牌过期时间保持在足够短的时间间隔内,并让正在运行的客户端跟踪并在必要时请求更新,那么 1 号将有效地作为一个完整的注销系统工作。这种方法的问题在于,它无法让用户在关闭客户端代码之间保持登录状态(取决于您设置的到期间隔时间)。

临时计划

如果发生紧急情况,或者用户令牌被泄露,您可以做的一件事是允许用户使用他们的登录凭据更改底层用户查找 ID。这将使所有关联的令牌无效,因为不再能够找到关联的用户。

我还想指出,在令牌中包含上次登录日期是个好主意,这样您就可以在一段时间后强制重新登录。

关于使用令牌进行攻击的相似之处/不同之处,这篇文章解决了这个问题:https ://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown

于 2014-04-15T16:49:11.630 回答
111

上面发布的想法很好,但是使所有现有 JWT 失效的一种非常简单的方法就是更改密钥。

如果您的服务器创建 JWT,使用机密 (JWS) 对其进行签名,然后将其发送给客户端,只需更改机密将使所有现有令牌无效,并要求所有用户获得新令牌以进行身份​​验证,因为他们的旧令牌突然变得无效根据到服务器。

它不需要对实际令牌内容(或查找 ID)进行任何修改。

显然,这仅适用于您希望所有现有令牌到期的紧急情况,因为每个令牌到期都需要上述解决方案之一(例如短令牌到期时间或使令牌内的存储密钥无效)。

于 2014-07-16T06:36:37.450 回答
99

这主要是一个很长的评论,支持并建立在@mattway 的答案之上

鉴于:

此页面上的其他一些建议的解决方案主张在每个请求上访问数据存储。如果您点击主数据存储来验证每个身份验证请求,那么我认为使用 JWT 而不是其他已建立的令牌身份验证机制的理由更少。如果您每次都访问数据存储区,您实际上已经使 JWT 成为有状态的,而不是无状态的。

(如果您的站点收到大量未经授权的请求,那么 JWT 会在不访问数据存储的情况下拒绝它们,这很有帮助。可能还有其他类似的用例。)

鉴于:

对于典型的现实世界 Web 应用程序,无法实现真正​​的无状态 JWT 身份验证,因为无状态 JWT 无法为以下重要用例提供即时安全的支持:

  • 用户的帐户被删除/阻止/暂停。

  • 用户密码已更改。

  • 用户的角色或权限已更改。

  • 用户已被管理员注销。

  • JWT 令牌中的任何其他应用程序关键数据都由站点管理员更改。

在这些情况下,您不能等待令牌到期。令牌失效必须立即发生。此外,您不能相信客户端不会保留和使用旧令牌的副本,无论是否有恶意。

所以:

我认为@matt-way 的答案#2 TokenBlackList 是向基于 JWT 的身份验证添加所需状态的最有效方法。

您有一个黑名单,其中包含这些令牌,直到它们的到期日期到期。与用户总数相比,令牌列表将非常小,因为它只需要保留列入黑名单的令牌直到它们到期。我会通过将无效的令牌放入 redis、memcached 或另一个支持在键上设置过期时间的内存数据存储中来实现。

对于通过初始 JWT 身份验证的每个身份验证请求,您仍然需要调用内存数据库,但您不必在其中存储整个用户集的密钥。(对于给定的网站来说,这可能是也可能不是什么大问题。)

于 2016-04-27T08:45:16.877 回答
62

我会在用户模型上记录 jwt 版本号。新的 jwt 令牌会将其版本设置为此。

当您验证 jwt 时,只需检查它的版本号是否等于用户当前的 jwt 版本。

任何时候你想使旧的 jwt 失效,只需增加用户的 jwt 版本号。

于 2014-06-15T23:46:18.047 回答
54

还没有尝试过,它使用了基于其他一些答案的大量信息。这里的复杂性是避免每次请求用户信息时都调用服务器端数据存储。大多数其他解决方案都需要对用户会话存储的每个请求进行数据库查找。在某些情况下这很好,但这是为了避免此类调用并使任何所需的服务器端状态非常小而创建的。您最终将重新创建一个服务器端会话,无论多么小以提供所有强制失效功能。但如果你想这样做,这里的要点是:

目标:

  • 减少使用数据存储(无状态)。
  • 能够强制注销所有用户。
  • 能够随时强制注销任何个人。
  • 能够在一定时间后要求重新输入密码。
  • 能够与多个客户合作。
  • 当用户从特定客户端单击注销时强制重新登录的能力。(为了防止有人在用户离开后“取消删除”客户端令牌 - 请参阅评论以获取更多信息)

解决方案:

  • 使用短期(<5m)访问令牌与长期(几个小时)客户端存储的 refresh-token配对。
  • 每个请求都会检查身份验证或刷新令牌到期日期的有效性。
  • 当访问令牌过期时,客户端使用刷新令牌刷新访问令牌。
  • 在刷新令牌检查期间,服务器会检查一个小的用户 ID 黑名单 - 如果发现则拒绝刷新请求。
  • 当客户端没有有效(未过期)的刷新或身份验证令牌时,用户必须重新登录,因为所有其他请求都将被拒绝。
  • 在登录请求时,检查用户数据存储是否被禁止。
  • 注销时 - 将该用户添加到会话黑名单,以便他们必须重新登录。您必须存储其他信息才能不将他们从多设备环境中的所有设备中注销,但可以通过将设备字段添加到用户黑名单。
  • 要在 x 时间后强制重新进入 - 在身份验证令牌中维护上次登录日期,并根据请求进行检查。
  • 要强制注销所有用户 - 重置令牌哈希键。

这需要你在服务器上维护一个黑名单(状态),假设用户表包含被禁止的用户信息。无效会话黑名单 - 是用户 ID 列表。仅在刷新令牌请求期间检查此黑名单。只要刷新令牌 TTL,条目就必须存在。一旦刷新令牌过期,用户将需要重新登录。

缺点:

  • 仍然需要对刷新令牌请求进行数据存储查找。
  • 对于访问令牌的 TTL,无效令牌可能会继续运行。

优点:

  • 提供所需的功能。
  • 在正常操作下,刷新令牌操作对用户隐藏。
  • 只需要对刷新请求而不是每个请求进行数据存储查找。即每 15 分钟 1 次,而不是每秒 1 次。
  • 将服务器端状态最小化为一个非常小的黑名单。

使用此解决方案,不需要像 reddis 这样的内存数据存储,至少不需要像您一样用于用户信息,因为服务器每 15 分钟左右才进行一次 db 调用。如果使用 reddis,在其中存储一个有效/无效的会话列表将是一个非常快速和简单的解决方案。不需要刷新令牌。每个身份验证令牌都有一个会话 ID 和设备 ID,它们可以在创建时存储在 reddis 表中,并在适当时失效。然后将对每个请求进行检查,并在无效时拒绝。

于 2016-03-29T03:54:10.463 回答
23

我一直在考虑的一种方法是在 JWT 中始终具有iat(发布于)值。然后,当用户注销时,将该时间戳存储在用户记录中。验证 JWT 时,只需将iat与上次注销的时间戳进行比较。如果iat是旧的,那么它是无效的。是的,你必须去数据库,但如果 JWT 在其他方面有效,我总是会提取用户记录。

我看到的主要缺点是,如果他们在多个浏览器中,或者也有移动客户端,它会将他们从所有会话中注销。

这也可能是使系统中的所有 JWT 失效的一个很好的机制。部分检查可能针对最后有效iat时间的全局时间戳。

于 2015-07-25T06:37:37.540 回答
15

我在这里有点晚了,但我认为我有一个不错的解决方案。

我的数据库中有一个“last_password_change”列,用于存储上次更改密码的日期和时间。我还将发布日期/时间存储在 JWT 中。验证令牌时,我会检查在颁发令牌后密码是否已更改,如果是,即使令牌尚未过期,令牌也会被拒绝。

于 2015-12-25T21:13:53.547 回答
14

----------------这个答案有点晚了,但可能会对某人有所帮助----------------

从客户端,最简单的方法是从浏览器的存储中删除令牌。

但是,如果你想销毁节点服务器上的令牌怎么办 -

JWT 包的问题在于它没有提供任何方法或方式来销毁令牌。对于上面提到的 JWT,您可以使用不同的方法。但是我在这里使用 jwt-redis。

因此,为了销毁服务器端的令牌,您可以使用jwt-redis 包而不是 JWT

这个库 (jwt-redis) 完全重复了库 jsonwebtoken 的全部功能,并增加了一个重要的功能。jwt-redis 允许您将 tokenIdentifier 存储在 redis 中以验证有效性。redis 中缺少 tokenIdentifier 会导致 token 无效。销毁jwt-redis中的token,有destroy方法

它以这种方式工作:

  1. 从 npm 安装 jwt-redis

  2. 去创造:

var redis = require('redis');

var JWTR =  require('jwt-redis').default;

var redisClient = redis.createClient();

var jwtr = new JWTR(redisClient);

const secret = 'secret';

const tokenIdentifier = 'test';

const payload = { jti: tokenIdentifier }; // you can put other data in payload as well

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });
  1. 验证:
jwtr.verify(token, secret);
  1. 摧毁:
// if jti passed during signing of token then tokenIdentifier else token 
jwtr.destroy(tokenIdentifier or token) 

注意

1)。您可以在令牌登录期间提供 expiresIn ,就像在 JWT 中提供的一样。

2)。如果在令牌签名期间未传递 jti,则 jti 由库随机生成。

可能这会对您或其他人有所帮助。谢谢。

于 2019-09-06T13:10:41.913 回答
7

您可以在用户文档/记录的数据库中拥有“last_key_used”字段。

当用户使用 user 登录并通过时,生成一个新的随机字符串,将其存储在 last_key_used 字段中,并在签署令牌时将其添加到有效负载中。

当用户使用令牌登录时,检查数据库中的 last_key_used 以匹配令牌中的那个。

然后,例如当用户注销时,或者如果您想使令牌无效,只需将“last_key_used”字段更改为另一个随机值,任何后续检查都将失败,从而迫使用户使用用户登录并再次通过。

于 2016-05-12T01:32:45.030 回答
7

保持这样的内存列表

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

如果您的令牌在一周内到期,则清除或忽略比这更早的记录。也只保留每个用户的最新记录。列表的大小将取决于您保留代币的时间以及用户撤销其代币的频率。仅在表更改时使用 db。应用程序启动时将表加载到内存中。

于 2018-07-02T19:01:58.017 回答
6

每个用户唯一的字符串,以及一起散列的全局字符串

作为 JWT 机密部分,允许单个和全局令牌失效。在请求身份验证期间以数据库查找/读取为代价的最大灵活性。也很容易缓存,因为它们很少改变。

这是一个例子:

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

例如用法见https://jwt.io(不确定他们处理动态 256 位机密)

于 2016-08-30T15:10:03.493 回答
5

聚会迟到了,经过一番研究,下面给出了我的两分钱。在注销期间,请确保正在发生以下事情...

清除客户端存储/会话

每当登录或注销分别发生时,更新用户表上次登录日期时间和注销日期时间。因此登录日期时间应始终大于注销(如果当前状态为登录且尚未注销,则保持注销日期为空)

这比保留额外的黑名单表和定期清除要简单得多。多设备支持需要额外的表格来保持登录、注销日期以及一些额外的细节,如操作系统或客户端细节。

于 2017-02-04T22:47:17.397 回答
5

为什么不直接使用 jti 声明(nonce)并将其作为用户记录字段存储在列表中(依赖于 db,但至少可以使用逗号分隔的列表)?无需单独查找,正如其他人所指出的那样,您可能还是想获取用户记录,这样您就可以为不同的客户端实例拥有多个有效令牌(“到处注销”可以将列表重置为空)

于 2016-03-09T15:01:59.737 回答
4

我是通过以下方式做到的:

  1. 生成一个unique hash,然后将其存储在redis和您的JWT中。这可以称为会话
    • 我们还将存储特定JWT发出的请求数- 每次将 jwt 发送到服务器时,我们都会增加请求整数。(这是可选的)

因此,当用户登录时,会创建一个唯一的哈希,存储在 redis 中并注入到您的JWT中。

当用户尝试访问受保护的端点时,您将从JWT中获取唯一的会话哈希,查询 redis 并查看它是否匹配!

我们可以从这里扩展并使我们的JWT更加安全,方法如下:

每个特定JWT发出的X请求,我们都会生成一个新的唯一会话,将其存储在我们的JWT中,然后将前一个会话列入黑名单。

这意味着JWT不断变化并阻止陈旧的JWT被黑客攻击、被盗或其他原因。

于 2016-10-26T08:49:18.110 回答
4

Kafka消息队列和本地黑名单

我考虑过使用像 kafka 这样的消息传递系统。让我解释:

例如,您可以拥有一个微服务(我们称之为userMgmtMslogin服务) ,它负责logout生成 JWT 令牌。然后这个令牌被传递给客户端。

现在客户端可以使用这个令牌来调用不同的微服务(我们称之为pricesMs),在pricesMs 中不会对users触发初始令牌创建的表进行数据库检查。此数据库必须仅存在于 userMgmtMs 中。此外,JWT 令牌应该包括权限/角色,以便价格无需从数据库中查找任何内容以允许 spring 安全性工作。

JwtRequestFilter 可以提供由 JWT 令牌中提供的数据创建的 UserDetails 对象(显然没有密码),而不是进入价格中的数据库。

那么,如何注销或使令牌无效?由于我们不想在每次请求priecesMs 时都调用userMgmtMs 的数据库(这会引入很多不需要的依赖项),因此解决方案可能是使用这个令牌黑名单。

我建议使用 kafka 消息队列,而不是保持这个黑名单为中心并依赖所有微服务中的一个表。

userMgmtMs 仍然负责,logout一旦完成,它就会将其放入自己的黑名单(一个不在微服务之间共享的表)。此外,它将带有此令牌内容的 kafka 事件发送到订阅所有其他微服务的内部 kafka 服务。

一旦其他微服务收到 kafka 事件,它们也会将其放入内部黑名单中。

即使某些微服务在注销时关闭,它们最终也会再次启动并在稍后的状态下接收消息。

由于开发了 kafka,因此客户端可以自己参考他们确实阅读了哪些消息,因此可以确保没有客户端、down 或 up 会错过任何这些无效令牌。

我能想到的唯一问题是 kafka 消息传递服务将再次引入单点故障。但这有点颠倒了,因为如果我们有一个全局表,其中保存了所有无效的 JWT 令牌,并且这个数据库或微服务关闭,则没有任何作用。使用 kafka 方法 + 客户端删除 JWT 令牌以进行正常用户注销,在大多数情况下,kafka 的停机时间甚至不会被注意到。由于黑名单作为内部副本分布在所有微服务中。

在关闭的情况下,您需要使被黑客入侵的用户无效并且 kafka 已关闭,这就是问题开始的地方。在这种情况下,将秘密更改为最后的手段可能会有所帮助。或者在这样做之前确保 kafka 已启动。

免责声明:我还没有实现这个解决方案,但不知何故,我觉得大多数提议的解决方案都否定了 JWT 令牌具有中央数据库查找的想法。所以我在考虑另一种解决方案。

请让我知道您的想法,这是否有意义或者是否有明显的原因无法解释?

于 2020-08-05T07:33:57.800 回答
4
  1. 为代币提供 1 天的到期时间
  2. 维护每日黑名单。
  3. 将失效/注销令牌放入黑名单

对于令牌验证,首先检查令牌过期时间,如果令牌未过期,则检查黑名单。

对于长会话需求,应该有一种延长令牌到期时间的机制。

于 2016-10-29T08:11:41.677 回答
3

如果您希望能够撤销用户令牌,您可以跟踪数据库上所有已发布的令牌,并检查它们在类似会话的表上是否有效(存在)。缺点是您将在每次请求时访问数据库。

我没有尝试过,但我建议使用以下方法来允许令牌撤销,同时将数据库命中率降至最低 -

要降低数据库检查率,请根据某种确定性关联将所有已发布的 JWT 令牌分成 X 组(例如,按用户 ID 的第一位数字划分为 10 个组)。

每个 JWT 令牌都将保存组 ID 和创建令牌时创建的时间戳。例如,{ "group_id": 1, "timestamp": 1551861473716 }

服务器将所有组 ID 保存在内存中,每个组都有一个时间戳,指示属于该组的用户的最后一次注销事件是什么时候。例如,{ "group1": 1551861473714, "group2": 1551861487293, ... }

将检查具有较旧组时间戳的 JWT 令牌的请求的有效性(DB 命中),如果有效,将发布具有新时间戳的新 JWT 令牌以供客户端将来使用。如果令牌的组时间戳更新,我们信任 JWT(无 DB 命中)。

所以 -

  1. 如果令牌具有旧的组时间戳,我们只使用数据库验证 JWT 令牌,而未来的请求将不会得到验证,直到用户组中的某人注销。
  2. 我们使用组来限制时间戳更改的数量(比如有一个用户登录和退出就像没有明天一样 - 只会影响有限数量的用户而不是所有人)
  3. 我们限制组的数量以限制内存中保存的时间戳数量
  4. 使令牌无效是一件轻而易举的事——只需将其从会话表中删除并为用户组生成一个新的时间戳。
于 2019-03-06T08:44:13.340 回答
3

如果“从所有设备注销”选项是可以接受的(在大多数情况下是这样):

  • 将令牌版本字段添加到用户记录。
  • 将此字段中的值添加到存储在 JWT 中的声明中。
  • 每次用户注销时增加版本。
  • 在验证令牌时,将其版本声明与存储在用户记录中的版本进行比较,如果不同则拒绝。

在大多数情况下,无论如何都需要进行一次数据库之旅来获取用户记录,因此这不会给验证过程增加太多开销。与维护黑名单不同,在黑名单中,由于需要使用连接或单独调用、清理旧记录等,因此数据库负载很大。

于 2019-04-09T17:37:49.413 回答
3

使用 JWT 的刷新...

我认为可行的一种方法是在数据库上存储刷新令牌(可以是 GUID)和对应的刷新令牌 ID(无论进行多少次刷新都不会改变),并将它们添加为正在生成用户的 JWT 时的用户。可以使用数据库的替代方案,例如内存缓存。但我在这个答案中使用了数据库。

然后,创建客户端可以在 JWT 到期之前调用的 JWT 刷新 Web API 端点。调用刷新时,从 JWT 中的声明中获取刷新令牌。

在对 JWT 刷新端点的任何调用中,验证当前刷新令牌和刷新令牌 ID 作为数据库上的一对。生成一个新的刷新令牌,并使用刷新令牌 ID 用它来替换数据库上的旧刷新令牌。请记住,它们是可以从 JWT 中提取的声明

从当前 JWT 中提取用户的声明。开始生成新 JWT 的过程。用新生成的刷新令牌替换旧刷新令牌声明的值,该刷新令牌也新保存在数据库中。有了所有这些,生成新的 JWT 并将其发送给客户端。

因此,在使用刷新令牌后,无论是预期用户还是攻击者,任何其他尝试在数据库上使用未配对的刷新令牌及其刷新令牌 ID,都不会导致生成新的 JWT,从而阻止具有该刷新令牌 ID 的任何客户端再使用后端,从而导致此类客户端(包括合法客户端)完全注销。

这解释了基本信息。

接下来要添加的是有一个窗口,用于刷新 JWT 的时间,以便该窗口之外的任何内容都是可疑活动。例如,窗口可以是 JWT 到期前 10 分钟。生成 JWT 的日期时间可以保存为该 JWT 本身的声明。并且当发生此类可疑活动时,即当其他人在窗口内已使用该刷新令牌ID之后尝试在窗口外或窗口内重用该刷新令牌ID时,应将刷新令牌ID标记为无效。因此,即使是刷新令牌 ID 的有效所有者也必须重新登录。

无法在数据库中找到与提供的刷新令牌 ID 配对的刷新令牌意味着刷新令牌 ID 应该无效。因为空闲用户可能会尝试使用攻击者已经使用过的刷新令牌。

如前所述,当用户尝试使用刷新令牌时,在目标用户之前被攻击者窃取和使用的 JWT 也会被标记为无效。

唯一未涵盖的情况是,即使攻击者可能已经窃取了客户端,客户端也从未尝试刷新其 JWT。但这不太可能发生在不受攻击者监管(或类似情况)的客户端上,这意味着攻击者无法预测客户端何时停止使用后端。

如果客户端启动通常的注销。应进行注销以从数据库中删除刷新令牌 ID 和相关记录,从而防止任何客户端生成刷新 JWT。

于 2020-08-30T17:43:29.433 回答
2

以下方法可以提供两全其美的解决方案:

让“立即”表示“~1 分钟”。

案例:

  1. 用户尝试成功登录:

    A. 给token添加“发行时间”字段,并根据需要保留到期时间。

    B. 存储用户密码哈希的哈希或在用户表中创建一个新字段,例如tokenhash将令牌哈希存储在生成的令牌中。

  2. 用户访问一个 url:

    A. 如果“发出时间”在“立即”范围内,则正常处理令牌。不要更改“发布时间”。根据“立即”的持续时间,这是一个容易受到攻击的持续时间。但是像一两分钟这样的短持续时间不应该太冒险。(这是性能和安全性之间的平衡)。三是不需要在这里打分贝。

    B. 如果令牌不在“立即”范围内,请根据数据库检查令牌哈希。如果没问题,请更新“发布时间”字段。如果不行,则不处理请求(最终强制执行安全性)。

  3. 用户更改令牌哈希以保护帐户。在“立即”的将来,该帐户是安全的。

我们将数据库查找保存在“立即”范围内。如果在“立即”持续时间内有来自客户端的请求突发,这是最有益的。

于 2020-07-24T17:54:11.003 回答
2

不使用 JWT 的刷新...

我想到了 2 种攻击场景。一是关于登录凭据的泄露。另一个是 JWT 的实际盗窃。

对于泄露的登录凭据,当发生新登录时,通常会向用户发送电子邮件通知。因此,如果客户不同意成为登录的人,则应建议他们重置凭据,这应将上次设置密码的日期时间保存到数据库/缓存中(并在用户在初始注册时设置密码)。每当授权用户操作时,应从数据库/缓存中获取用户更改密码的日期时间,并与生成给定 JWT 的日期时间进行比较,并禁止对在所述凭据重置日期时间之前生成的 JWT 执行操作,因此基本上使此类 JWT 无用。这意味着将生成 JWT 的日期时间保存为 JWT 本身的声明。在 ASP.NET Core 中,可以使用策略/要求来进行此比较,如果失败,则禁止客户端。因此,每当完成凭据重置时,这都会在全局范围内注销后端用户。

对于 JWT 的实际盗窃...... JWT的盗窃并不容易被发现,但过期的 JWT 很容易解决这个问题。但是如何在 JWT 过期之前阻止攻击者呢?它具有实际的全局注销。它类似于上面描述的凭据重置。为此,通常将用户启动全局注销的日期时间保存在数据库/缓存中,并在授权用户操作时,获取它并将其与给定 JWT 的生成日期时间进行比较,并禁止该操作在上述全局注销日期时间之前生成的 JWT,因此基本上使此类 JWT 无用。如前所述,这可以使用 ASP.NET Core 中的策略/要求来完成。

现在,您如何检测 JWT 被盗?我现在对此的回答是偶尔提醒用户全局注销并再次登录,因为这肯定会使攻击者注销。

于 2020-09-01T14:49:47.200 回答
2

使令牌无效的好方法仍然需要数据库访问。用于包括用户记录的某些部分何时更改的目的,例如更改角色、更改密码、电子邮件等。可以在用户记录中添加一个modifiedorupdated_at字段,该字段记录了此更改的时间,然后您将其包含在声明中。因此,当 JWT 被验证时,您将声明中的时间与数据库中记录的时间进行比较,如果声明的时间在之前,则令牌无效。这种方法也类似于将 存储iat在数据库中。

注意:如果您使用modifiedorupdated_at选项,那么您还必须在用户登录和注销时对其进行更新。

于 2021-08-04T09:34:47.040 回答
0

在此示例中,我假设最终用户也有一个帐户。如果不是这种情况,那么该方法的其余部分不太可能奏效。

当您创建 JWT 时,将其保存在与正在登录的帐户关联的数据库中。这确实意味着您可以仅从 JWT 中提取有关用户的其他信息,因此取决于环境,这可能会也可能不会没事。

在之后的每个请求中,您不仅执行(我希望)您使用的任何框架附带的标准验证(验证 JWT 是否有效),它还包括用户 ID 或其他令牌(需要匹配数据库中的那个)。

当您注销时,删除 cookie(如果使用)并使数据库中的 JWT(字符串)无效。如果无法从客户端删除 cookie,那么至少注销过程将确保令牌被销毁。

我发现这种方法,再加上另一个唯一标识符(因此数据库中有 2 个持久项,并且可供前端使用),会话非常有弹性

于 2020-08-05T07:25:34.060 回答
0

另一种方法是为关键 API 端点提供一个中间件脚本。
如果令牌被管理员无效,此中间件脚本将检查数据库。
对于不需要立即完全阻止用户访问的情况,此解决方案可能很有用。

于 2020-08-04T09:10:47.937 回答
0

IAM 解决方案,如 Keycloak(我曾研究过)提供令牌撤销端点,如

令牌撤销端点 /realms/{realm-name}/protocol/openid-connect/revoke

如果您只是想注销用户代理(或用户),您也可以调用端点(这只会使令牌无效)。同样,在 Keycloak 的情况下,依赖方只需要调用端点

/realms/{realm-name}/protocol/openid-connect/logout

如果您想了解更多信息,请链接

于 2020-06-10T21:15:36.943 回答
0

即使您将令牌从存储中删除,它仍然有效,但仅在短时间内有效,以减少被恶意使用的可能性。

您可以创建一个deny-listing,一旦从存储中删除令牌,您就可以将令牌添加到此列表中。如果您有一个微服务服务,则使用此令牌的所有其他服务都必须添加额外的逻辑来检查此列表。这将集中您的身份验证,因为每个服务器都必须检查一个集中的数据结构。

于 2022-03-03T21:31:56.970 回答
0

我将回答如果我们在使用 JWT 时需要从所有设备功能中提供注销功能。这种方法将对每个请求使用数据库查找。因为即使发生服务器崩溃,我们也需要持久的安全状态。在用户表中,我们将有两列

  1. LastValidTime(默认:创建时间)
  2. 登录(默认:true)

每当用户发出注销请求时,我们会将 LastValidTime 更新为当前时间,并将 Logged-In 更新为 false。如果有登录请求,我们不会更改 LastValidTime,但 Logged-In 将设置为 true。

当我们创建 JWT 时,我们将在有效负载中拥有 JWT 创建时间。当我们授权服务时,我们将检查 3 个条件

  1. JWT 是否有效
  2. JWT 有效负载创建时间是否大于用户 LastValidTime
  3. 用户是否登录

让我们看一个实际的场景。

用户 X 有两台设备 A、B。他在晚上 7 点使用设备 A 和设备 B 登录到我们的服务器。(假设 JWT 过期时间为 12 小时)。A 和 B 都有 JWT 和 createdTime : 7pm

晚上 9 点,他丢失了设备 B。他立即从设备 A 注销。这意味着现在我们的数据库 X 用户条目的 LastValidTime 为“ThatDate:9:00:xx:xxx”,Logged-In 为“false”。

在 9:30,Mr.Thief 尝试使用设备 B 登录。即使登录为假,我们也会检查数据库,因此我们不会允许。

晚上 10 点,X 先生从他的设备 A 登录。现在设备 A 具有创建时间:晚上 10 点的 JWT。现在数据库登录设置为“真”

在晚上 10:30,Mr.Thief 尝试登录。即使 Logged-In 是真的。数据库中的 LastValidTime 是晚上 9 点,但 B 的 JWT 将时间创建为晚上 7 点。所以他不会被允许访问该服务。因此,在没有密码的情况下使用设备 B,他无法在一台设备注销后使用已创建的 JWT。

于 2020-05-11T18:10:05.920 回答
0

以下是无需在每个请求上调用数据库的方法:

  • 将有效令牌的哈希图保存在内存缓存中(例如,大小有限的 LRU)
  • 检查令牌时:如果令牌在缓存中,立即返回结果,不需要数据库查询(大多数情况)。否则执行全面检查(查询数据库,检查用户状态和无效令牌...)。然后更新缓存。
  • 使令牌无效时:将其添加到数据库中的黑名单中,然后更新缓存,如果需要,向所有服务器发送信号。

请记住,缓存应该具有有限的大小,例如 LRU,否则您可能会耗尽内存。

于 2021-08-03T14:45:05.700 回答
-1

如果不对每个令牌验证进行数据库查找,这似乎真的很难解决。我能想到的替代方法是在服务器端保留一个无效令牌的黑名单;每当发生更改时,都应该在数据库上进行更新,以便在重新启动时保持更改,方法是让服务器在重新启动时检查数据库以加载当前的黑名单。

但是,如果您将它保存在服务器内存中(某种全局变量),那么如果您使用多个服务器,它将无法跨多个服务器进行扩展,因此在这种情况下,您可以将其保存在共享的 Redis 缓存中,这应该是设置将数据保存在某个地方(数据库?文件系统?),以防它必须重新启动,并且每次启动新服务器时,它都必须订阅 Redis 缓存。

作为黑名单的替代方案,使用相同的解决方案,您可以使用每个会话保存在 redis 中的哈希来执行此操作,正如其他答案所指出的那样(但不确定对于许多用户登录会更有效)。

听起来是不是很复杂?它对我有用!

免责声明:我没有使用过 Redis。

于 2019-09-02T21:16:18.760 回答
-1

如果您需要logout基于 JWT 的登录的快速、高效和优雅的功能(这实际上是使 JWT 无效的主要原因),方法如下:用 1 秒内到期的令牌替换当前令牌。

详细说明:

  1. 当您/logout从附加了有效 JWT 的客户端发出请求时,请删除您保留在本地存储中的 JWT(如果您为“记住我”功能而保留任何 JWT)。

  2. 然后当请求到达服务器/logout路由处理程序时:

    • 如果传入的 JWT 有效,则创建 1 秒后到期的新 JWT 并将其发送回客户端。
    • 您可以使用redirect响应和响应正文中的令牌来执行此操作。重定向可以是任何公共路线(比如。/home)。或者您可以使用正常的非重定向响应进行响应,以便稍后在客户端上进行所需的重定向。
  3. 现在在客户端

    • 当您收到响应时,将新的 JWT 保存在您刚刚在步骤 1 中删除旧 JWT 的本地存储中。现在,如果需要,您可以在客户端上重定向,以免使用来自服务器的重定向响应。
    • 由于新令牌已经过期(因为在 1 秒内设置了过期时间),任何重定向到受保护路由的尝试(带有已经过期的令牌)都应该重定向到注册/登录页面。您必须自己在受服务器身份验证保护的路由上提供此行为。

就如此容易。您刚刚使用 JWT 获得了通常的按需注销功能。

是的,它不会使原始 JWT 无效。如果有人(攻击者)在他仍然可以使用它之前劫持并保存了它,直到它过期。然而,他的机会之窗正在随着时间的推移而缩小。

但是,解决方案的第二部分出现了:一个非常短暂(10 分钟?)的原始 JWT 和较长寿命的刷新令牌保存在数据库中,实际上被撤销或从数据库中删除。

或者,可以在此处使用将原始 JWT 或类似方法列入黑名单/白名单。

快速解决方案可以应用于失败者的安全要求。可以为更严格的安全要求情况添加刷新令牌/黑名单/白名单部分。

无论如何,它是更简单且按需可扩展的方法。

于 2020-12-29T15:08:41.380 回答
-2

如果您使用 axios 或类似的基于 Promise 的 http 请求库,您可以简单地在部件内部的前端销毁令牌.then()。用户执行此函数后,它将在响应 .then() 部分启动(来自服务器端点的结果代码必须是 ok,200)。用户在搜索数据时单击此路由后,如果数据库字段user_enabled为 false,则会触发销毁令牌,用户将立即注销并停止访问受保护的路由/页面。当用户永久登录时,我们不必等待令牌过期。

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}
于 2019-11-14T15:29:25.220 回答
-2

我只是将令牌保存到用户表中,当用户登录时我将更新新令牌,并且当 auth 等于用户当前 jwt 时。

我认为这不是最好的解决方案,但对我有用。

于 2018-05-22T15:04:58.007 回答