处理JWT到期和JWT有效负载更新

Jim*_*m-Y 10 javascript node.js jwt koa

我的个人/爱好应用程序有一个基于Koa的Node.js后端.

我用JWT令牌实现了会话处理.客户端(AngularJS)在成功登录后获取令牌并将令牌存储在某处(当前存在sessionStorage但是出于此问题的目的,它应该无关紧要).

我有两个问题:

  1. 当我需要更新JWT所代表的用户记录时,比如说,用户打开了双因素身份验证(2FA),所以我让他提供他的电话号码,我想在用户的记录中设置这个电话号码.目前,在成功验证电话号码后,我调用后端更新用户记录,并使用更新的用户记录创建新的JWT令牌(我从JWT令牌中排除敏感信息,如散列密码,但我想要包括客户端使用的电话号码).当某些凭据更改并使用此新令牌更新现有客户端令牌时,是否可以创建新令牌?我是否永远不会创建另一个令牌,只创建一个令牌并且只有在成功验证后?然后,我如何更新令牌中的有效负载?

  2. 我该如何处理过期的JWT令牌?在我看来,我有3个(可能的)场景:

    2.1.JWT准备短暂生活,比如说15分钟.如果后端服务器回复401 Unauthenticated'Invalid token'(我猜这是默认行为koa-jwt),那么我会自动注销我的客户端并要求重新验证.但我还建立了一个互补的中间件,它是后端链中的最后一个,用刷新的到期重新创建令牌,客户端也会用刷新的令牌替换现有的令牌.因此,如果用户处于活动状态并且每个受保护的API调用都使用该应用程序,则在成功的情况下,将创建新令牌以替换旧令牌.

    2.2.JWT设置为长寿命,比如1周,如果它到期,我选择从客户端重新进行身份验证.

    2.3.复制https://tools.ietf.org/html/rfc6749#section-1.5.在成功验证后创建JWT令牌时,我们发送access_token和refresh_token.当access_token过期并且服务器以HTTP 401 '无效令牌'(koa-jwt默认)响应时,客户端将refresh_token发送到后端以要求新的access_token(以及可选地新的refresh_token).在这种情况下,我不完全理解如何针对旧的access_token验证refresh_token以提供新令牌?或者为什么我们需要一个refresh_token?

关于上层主题(JWT更新和JWT到期)的任何通用建议都会有所帮助.

Joã*_*elo 6

从底部开始,我会忽略刷新令牌,因为我认为他们不会在这里帮助你.它们通常针对客户端应用程序可以提供比用户浏览器更安全的存储的其他场景 - 考虑本机移动应用程序或服务器端Web应用程序.

刷新令牌是长寿的.这意味着当客户端从服务器获取一个时,必须安全地存储此令牌以防止其被潜在的攻击者使用,因此将它们存储在浏览器中是不安全的.

(重点是我的;源刷新令牌)

这意味着选项2.3基本上与2.2相同,这不是一个糟糕的选择.拥有较长会话持续时间的Web应用程序并不罕见.如果您的应用程序不是非常敏感,则可以使用长会话来改善用户体验.例如,Django使用其会话cookie年龄的默认值为两周.见SESSION_COOKIE_AGE.

剩下的选项(2.1)通常称为滑动会话.会话超时很短,但只要用户在该时间间隔内继续使用该应用程序,会话就会自动更新.这可能是最常用的方法,或者至少是我用过的时间,所以我有偏见.我唯一要注意的是,滑动会话通常使用不透明的会话标识符实现,客户端存储为cookie,然后存储在服务器上的实际会话数据.

您的方法有点不同,因为您有一个存储在浏览器本地存储上的无状态JWT令牌(它包含实际用户数据).就像你说的那样,为了更新令牌,你必须生成一个新令牌,因为你必须生成一个新的签名.

签名用于验证JWT的发件人是否是其所说的人,并确保邮件未被更改.

(重点是我的;源JSON网络令牌)

说了这么多,我会考虑以下几点:

  1. 问问自己是否真的需要JWT,或者将常规会话标识符存储为cookie(仅限HTTP)会简化您的逻辑.
  2. 例如,如果JWT是一个要求,你有另一个API也会接受这些令牌作为身份验证,那么我会考虑选项2.1或2.2作为基于浏览器的应用程序的刷新令牌.

话虽如此,你还应该考虑JWT不是很大,但如果你决定自动更新,它们仍然是一个开销.您可以通过选择20分钟的会话持续时间来缓解此问题,并且仅在会话结束一半后执行自动续订.

另一点是,应用程序中的XSS之类的漏洞会将访问令牌暴露给攻击者,因为注入的脚本可以从localStorage/ 读取sessionStorage,这可能是支持仅HTTP会话cookie存储的另一点.


Vik*_*ash 4

在回答第一个问题之前,我想先回答你的第二个问题。

基本上,您提到的第三个选项是更新访问令牌的最佳方法。访问令牌的寿命应较短(约 5 分钟),而刷新令牌的寿命应较长。当您的访问令牌过期时,将刷新令牌发送到后端并获取新的访问令牌。所以你的回应应该是这样的:

{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI4NjYsImV4cCI6MTQ0NDI2Mjg4Nn0.Dww7TC-d0teDAgsmKHw7bhF2THNichsE6rVJq9xu_2s",
"expires_in":10,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696b"
}
Run Code Online (Sandbox Code Playgroud)

因此,我们的想法是将您的应用程序分为授权服务器(生成访问令牌/刷新令牌)和资源服务器(验证访问令牌并访问资源)。您可以维护一个架构来根据授权服务器中的访问令牌验证刷新令牌。请参阅此链接中提到的架构部分,这可能会给您一些想法。Oauth2。您可以根据需要修改架构。您无需为每个请求调用发送刷新令牌以及访问令牌。刷新令牌只能发送到授权服务器以生成新的访问令牌。如何生成刷新令牌?如果我使用 Java,我将用来UUID.randomUUID()生成唯一的刷新令牌。

现在回答您的第一个问题,如果您想根据更新的用户记录更新 JWT 有效负载,那么您可以使用相同的刷新令牌来生成具有更新的有效负载的新访问令牌。逻辑保持不变,因为如果用户记录中存在电话号码,则会将其添加到有效负载中,如果不存在,则有效负载中的电话号码将为空。

使用刷新令牌的主要优点是可以随时使用刷新令牌更新访问令牌