处理JWT过期和JWT负载更新

10

我有一个基于Koa的Node.js后端,用于我的个人/业余应用程序。

我使用JWT令牌实现会话管理。客户端(AngularJS)在成功登录后获取令牌并将其存储在某个地方(目前在sessionStorage中,但对于本问题而言,这不重要)。

我有两个问题:

  1. 当我需要更新JWT所代表的用户记录时,例如,用户打开了双因素身份验证(2FA),所以我要求他提供手机号码,并将此电话号码设置在用户记录中。当前,在电话号码成功验证后,我调用我的后端来更新用户记录,并创建一个具有更新后的用户记录的新JWT令牌(我从JWT令牌中排除敏感信息,如哈希密码,但我想包括电话号码以供客户端使用)。更改凭据时创建新令牌并使用此新令牌更新现有客户端令牌是否可以?我应该永远不要创建另一个令牌,只有在成功认证时才创建唯一的令牌吗?那我如何更新令牌中的有效负载?

  2. 我应该如何处理过期的JWT令牌?在我的脑海中,我有3种(可能的)情况:

2.1. JWT被设置为短暂的,例如15分钟。如果后端服务器回复401未经授权'无效令牌'(我猜这是koa-jwt的默认行为),那么我会自动注销我的客户端并要求重新认证。但我还设置了一个补充中间件,在后端链中是最后一个,以重新创建具有刷新过期时间的令牌,并且客户端也将用刷新后的令牌替换现有令牌。因此,如果用户活跃并在每个受保护的API调用时使用应用程序,则在成功情况下,将创建新令牌以替换旧令牌。

2.2. JWT被设置为长期存在,例如1周,如果过期,我选择从客户端进行重新认证。

2.3. 复制https://www.rfc-editor.org/rfc/rfc6749#section-1.5。在成功进行身份验证后创建JWT令牌时,我们发送一个access_token以及一个refresh_token。当access_token过期且服务器响应HTTP 401'invalid token' (koa-jwt默认)时,客户端会将refresh_token发送到后端请求一个新的access_token(和可选的新refresh_token)。在这种情况下,我不太明白如何针对旧的access_token验证refresh_token以提供一个新的token?或者为什么我们需要有一个refresh_token?

任何关于JWT更新和JWT过期的通用建议都会很有帮助。


为什么不直接使用cookie? - Kebman
2个回答

6
从底层开始,我会忽略刷新令牌,因为我认为它们对你没有帮助。它们通常针对其他场景,其中客户端应用程序可以提供比用户浏览器更安全的存储--例如本机移动应用程序或服务器端Web应用程序。
刷新令牌是长期有效的。这意味着当客户端从服务器获取一个时,必须将此令牌安全地存储起来,以防止潜在攻击者使用。因此,在浏览器中存储它们不安全。
(强调是我的;来源刷新令牌
这意味着选项2.3基本上与2.2相同,这不是一个坏选择。拥有长时间会话持续时间的Web应用程序并不罕见。如果您的应用程序不是高度敏感的,则可以使用长时间会话来改善用户体验。例如,Django将其会话cookie的年龄默认设置为两周。请参见SESSION_COOKIE_AGE
剩下的选项(2.1),通常被称为滑动会话。会话超时时间很短,但只要用户在该间隔内继续使用应用程序,会话就会自动更新。这可能是最常见的方法,或者至少是我使用最多的方法,所以我有偏见。唯一需要注意的是,滑动会话通常使用存储在客户端作为cookie的不透明会话标识符实现,然后将实际会话数据存储在服务器上。
您的方法有点不同,因为您拥有一个无状态JWT令牌(它包含实际用户数据)存储在浏览器本地存储中。正如您所说,为了更新令牌,您必须生成一个新令牌,因为您必须生成一个新签名。
引用:
“该签名用于验证JWT的发送方是否是其所说的人,并确保消息未被更改。”
(强调是我的;来源JSON web tokens
说了这么多,我建议考虑以下内容:
  1. 问问自己是否真的需要JWT,或者普通的会话标识符存储为cookie(HTTP Only)是否可以简化您的逻辑。
  2. 如果需要JWT,例如您有另一个API也将接受这些令牌作为身份验证,则我会考虑选项2.1或2.2作为基于浏览器的应用程序的刷新令牌不建议使用。

话虽如此,您还应考虑到JWT并不是很大,但如果您决定自动更新,它们仍然会成为一种开销。您可以通过选择20分钟的会话持续时间,并在会话过了一半后才执行自动更新来稍微缓解这种情况。

另一个问题是,应用程序中的XSS等漏洞将使访问令牌暴露给攻击者,因为注入的脚本将能够从localStorage/sessionStorage读取,这可能是支持HTTP only会话cookie存储的另一个优点。


4

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

基本上,你提到的第三个选项是更新访问令牌的最佳方式。访问令牌应该是短暂的(~5分钟),而刷新令牌则有更长的生命周期。当你的访问令牌过期时,将刷新令牌发送到后端并获取新的访问令牌。因此,你的响应应该类似于这样:

{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI4NjYsImV4cCI6MTQ0NDI2Mjg4Nn0.Dww7TC-d0teDAgsmKHw7bhF2THNichsE6rVJq9xu_2s",
"expires_in":10,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696b"
}

因此,这个想法是将您的应用程序分成授权服务器(生成访问令牌/刷新令牌)和资源服务器(验证访问令牌并访问资源)。您可以维护模式以验证授权服务器中的刷新令牌与访问令牌。请参考此链接中提到的模式部分,这可能会给您一些想法。Oauth2。您可以根据需要修改模式。您不需要在每个请求调用中随附刷新令牌和访问令牌。刷新令牌只能发送到授权服务器以生成新的访问令牌。如何生成刷新令牌?如果我使用Java,我会使用UUID.randomUUID()来生成唯一的刷新令牌。
现在回答您的第一个问题,如果您想根据更新后的用户记录更新JWT负载,则可以使用相同的刷新令牌生成具有更新负载的新访问令牌。逻辑保持不变,因为如果电话号码存在于用户记录中,则会将其添加到负载中,如果不存在,则在负载中为null。
使用刷新令牌的主要优点是可以使用刷新令牌随时更新访问令牌。

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