使JSON Web令牌无效

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

该项目是一个利用socket.io的游戏 – 在一个会话中会有多个通信通道(web和socket.io)的情况下,基于令牌的会话将非常有用。

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

我也想了解我应该用这种模式去寻找哪些常见(或不常见)的陷阱/攻击。 例如,如果这种模式容易受到与基于会话存储/ cookie的方法相同/不同types的攻击的影响。

所以,说我有以下(改编自这个和这个 ):

会话存储login:

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}); }); } 

基于令牌的login:

 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数据库。

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

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

1)简单地从客户端删除令牌

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

2)创build一个令牌黑名单

您可以存储无效标记,直到它们的初始失效date,并将它们与传入的请求进行比较。 这似乎抵消了首先完全实现令牌的原因,因为您需要为每个请求触摸数据库。 虽然存储大小可能会更低,因为您只需要存储注销和到期时间之间的令牌(这是一种直觉,并且绝对依赖于上下文)。

3)保持令牌过期时间短并经常旋转

如果在足够短的时间间隔内保持令牌到期时间,并且运行中的客户端在必要时保持跟踪和请求更新,则号码1将有效地用作完整的注销系统。 这种方法的问题是,它使得用户无法保持loginclosures客户端代码(取决于你到期时间间隔)。

临时计划

如果遇到紧急情况,或者用户令牌被盗用,您可以做的一件事就是允许用户使用其login凭据更改基础用户查找ID。 这将使所有相关的令牌无效,因为关联的用户将不能再被find。

我还想指出,将最后一次logindate与令牌包含在一起是一个好主意,以便在一段时间之后能够执行重新login。

就使用标记的攻击方面的相似/差异而言,这篇文章解决了这个问题: http : //blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

上面提到的想法是好的,但是一个简单而简单的方法来使所有现有的智威汤逊无效,只是改变了秘密。

如果你的服务器创build了JWT,用一个秘密(JWS)标记它,然后把它发送给客户端,只要改变秘密就会使所有现有的令牌失效,并要求所有的用户获得一个新的令牌,到服务器。

它不需要对实际标记内容(或查找标识)进行任何修改。

显然这只适用于当所有现有令牌过期时的紧急情况,因为每个令牌到期需要上述解决scheme之一(例如令牌过期时间过短或令牌内存储的密钥无效)。

这主要是一个长期的评论,支持和@mattway回答build设

鉴于:

在这个页面上的其他一些build议的解决scheme主张在每个请求上触击数据库。 如果您点击主数据存储以validation每个authentication请求,那么我看不到使用JWT的原因,而不是其他build立的令牌authentication机制。 你基本上使JWT成为有状态的,而不是每次去数据存储时都是无状态的。

(如果您的网站收到大量的未经授权的请求,那么JWT会拒绝他们,而不会触及数据存储区,这很有帮助,可能还有其他用例。

鉴于:

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

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

用户的密码已更改。

用户的angular色或权限已更改。

用户由pipe理员注销。

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

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

因此:我认为@ matt-way,#2 TokenBlackList的答案是将基于JWT的身份validation添加到所需状态的最有效方法。

你有一个黑名单持有这些令牌,直到它们的到期date被击中。 令牌的列表与用户总数相比是相当小的,因为它只需要保留黑名单,直到它们到期。 我通过将无效令牌放入redis,memcached或另一个支持在密钥上设置过期时间的内存数据存储来实现。

对于每个通过初始JWTvalidation的validation请求,您仍然需要对内存数据库进行调用,但是不必为整个用户组存储密钥。 (对于一个给定的网站,这可能是一件大事,也可能不是一件大事)

我会logging用户模型上的jwt版本号。 新的jwt tokens会将它们的版本设置为这个。

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

任何时候你想使旧的jwts失效,只是碰到用户的jwt版本号。

还没有尝试过,它是根据一些其他的答案使用了大量的信息。 这里的复杂性是为了避免每个请求用户信息的服务器端数据存储调用。 大多数其他解决scheme需要每个请求的数据库查find用户会话存储。 在某些情况下,这是很好的,但是这是为了避免这种调用而创build的,并且使所需的服务器端状态非常小。 您将最终重新创build一个服务器端会话,尽pipe很小,以提供所有的强制失效function。 但是如果你想这样做的话就是要点:

目标:

  • 缓解数据存储的使用(无状态)。
  • 能够强制注销所有用户。
  • 能够随时强制退出任何个人。
  • 能够在一段时间后要求重新input密码。
  • 能够与多个客户端合作。
  • 能够在用户从特定客户端单击注销时强制重新login。 (为防止用户离开后有人“删除”客户令牌 – 请参阅注释以获取更多信息)

解决scheme:

  • 使用与较长寿命(less于几小时)的客户端存储的刷新令牌配对的短命(<5m)访问令牌 。
  • 每个请求都会检查auth或refresh令牌的有效期限是否有效。
  • 当访问令牌到期时,客户端使用刷新令牌刷新访问令牌。
  • 在刷新令牌检查期间,服务器检查用户标识符的一个小黑名单 – 如果发现拒绝刷新请求。
  • 当客户端没有有效(未过期)的刷新或授权令牌时,用户必须重新login,因为所有其他请求都将被拒绝。
  • 在login请求中,检查用户数据存储是否禁用。
  • 在注销时 – 将该用户添加到会话黑名单,以便他们必须重新login。您将不得不存储额外的信息,以便在多设备环境中不将所有设备注销,但可以通过向设备添加设备字段用户黑名单。
  • 要在x时间后强制重新input – 在身份validation令牌中保留上次logindate,并根据请求进行检查。
  • 要强制注销所有用户 – 重置令牌散列密钥。

这要求您在服务器上维护一个黑名单(状态),假定用户表包含禁止的用户信息。 无效会话黑名单 – 是用户ID的列表。 这个黑名单只在刷新令牌请求期间被检查。 只要刷新标记TTL,条目就需要在其上生存。 一旦刷新令牌到期,用户将被要求重新login。

缺点:

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

优点:

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

有了这个解决scheme,像redis这样的内存数据存储是不需要的,至less不像用户信息那样,因为服务器每15分钟只做一个db调用。 如果使用reddis,在这里存储一个有效/无效的会话列表将是一个非常快速和简单的解决scheme。 不需要刷新令牌。 每个authentication令牌都有一个会话ID和设备ID,它们可以存储在创build时的reddis表中,并在适当时失效。 然后他们将被检查每个请求,并在无效时被拒绝。

我在这里有点晚,但我想我有一个体面的解决scheme。

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

我一直在考虑的方法是在智威汤逊总是有一个iat (发出)的价值。 然后,当用户注销时,将该时间戳存储在用户logging上。 在validationJWT时,只需比较iat和上次注销的时间戳即可。 如果iat比较老,那么它是无效的。 是的,你必须去数据库,但是如果智威汤逊是有效的,我总是会拉用户logging。

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

这也可能是使系统中的所有JWT失效的好机制。 部分支票可能违背上次有效的全球时间戳。

您可以在用户的​​文档/logging上在数据库上有“last_key_used”字段。

当用户使用用户login并通过时,生成一个新的随机string,将其存储在last_key_used字段中,并在签名令牌时将其添加到有效内容中。

当用户使用令牌login时,请检查数据库中的last_key_used以匹配令牌中的last_key_used。

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

  1. 为令牌提供1天的到期时间
  2. 保持每日黑名单。
  3. 把无效/注销令牌放入黑名单

对于令牌validation,首先检查令牌到期时间,然后检查令牌是否过期的黑名单。

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

为什么不使用jti声明(nonce)并将其作为用户logging字段存储在列表中(db依赖,但至less用逗号分隔的列表是好的)? 不需要单独的查询,因为其他人已经指出,无论如何你想获得用户logging,这样你可以有多个有效的令牌为不同的客户端实例(“注销无处不在”可以重置列表为空)

我是这样做的:

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

因此,当用户login时,会创build一个唯一的哈希值,存储在redis中并注入到JWT中

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

我们可以扩大这个范围,使我们的JWT更加安全,这就是:

每个X请求一个特定的JWT ,我们产生一个新的独特的会话,存储在我们的JWT ,然后黑名单上一个。

这意味着JWT正在不断变化,停止JWT遭到黑客入侵,被盗或其他事情的发生。

晚会之后,经过一番研究,我的两美分就在下面给出。 在注销期间,确保以下事情正在发生…

清除客户端存储/会话

更新用户表的最后logindate时间和注销date时间每次login或注销发生时分别。 所以login的date时间总是应该大于注销(或保持注销date为空,如果当前状态是login,还没有注销)

这比保留额外的黑名单表和定期清除要简单得多。 多设备支持需要额外的表保持login,注销date与一些额外的细节,如操作系统或客户端的详细信息。

唯一的每个用户string和全局string散列在一起

作为智威汤逊秘密部分允许个人和全球令牌无效。 请求身份validation过程中以db查找/读取为代价获得最大的灵活性 也很容易caching,因为它们很less变化。