使用 JWT 令牌时应该如何处理 RESTful 身份验证?

Posted

技术标签:

【中文标题】使用 JWT 令牌时应该如何处理 RESTful 身份验证?【英文标题】:How should I handle RESTful authentication while using JWT tokens? 【发布时间】:2019-08-31 05:39:22 【问题描述】:

我读过很多文章,看过很多视频,但有很多矛盾之处。我尽量避免使用任何外部库并从头开始构建系统,我已经阅读了有关 oAuth 2 的信息,但它更令人困惑。

这是我认为到目前为止还可以的流程:

    用户使用电子邮件和密码填写表单并提交。 服务器验证密码是否匹配,并返回一个带有签名 jwt 令牌的 httponly cookie,该令牌在 10 后过期 分钟。 (我知道我必须保护它免受 csrf 攻击) 用户登录后,他向服务器发出的每个新请求都会自动发送标头中的 cookie,并且 服务器将验证令牌。

一切都很好,但我遇到了一些问题并有一些疑问:

我希望用户即使在打开新会话后仍保持登录状态,因此在令牌过期或关闭浏览器后无需登录。

如果访问令牌过期了怎么办?

应该有一个刷新令牌附加到数据库中的用户,当用户登录并过期 7 天时添加,然后服务器将响应包含该刷新令牌的 cookie?

在访问令牌过期的新请求中,用户将刷新cookie发送到服务器,如果它与用户数据库刷新令牌匹配,服务器将响应一个单独的cookie来更新访问令牌?

如果有刷新令牌,您应该将其存储在哪里以及采用什么格式? (cookie、数据库还是在哪里?)

我应该根据这个刷新令牌cookie保持用户登录吗?如果是httponly我无法读取它并设置用户登录的状态。我应该怎么做?

我听说撤销 jwt 令牌是有问题的。你会如何解决它?

这整个过程你会怎么做?请解释一下工作流程,我尽量避免使用本地存储,因为我到处阅读对敏感数据不安全的地方。

【问题讨论】:

【参考方案1】:

我已经实施并部署到生产系统,这些系统完全可以执行您在此处询问的各种事情,因此我认为我有资格为您提供一些指导来解决您的特定问题并回答您的问题。到目前为止,您在编号列表中列出的流程绝对是正确的路径。我确实理解你从那里开始的困惑,因为有很多不同的选择可以解决这个问题。

除了在用户向服务器提交登录表单时提供向客户端返回新 JWT 的登录路由之外,我还建议实现一个令牌刷新路由,该路由接受从初始接收到的仍然有效的 JWT登录过程并返回具有更新过期时间的新 JWT。此新令牌刷新路由的逻辑应首先通过将其与数据库中的用户匹配来验证提供的 JWT 是否仍然有效。然后,它应该使用与登录路由逻辑相同的 JWT 生成逻辑生成一个新令牌。然后,应用程序应该覆盖数据库中的访问令牌数据,以便用户用新生成的访问令牌替换旧的访问令牌。一旦旧的访问令牌不再有效,就没有必要在数据库中保留它,这就是为什么我建议简单地用新的访问令牌覆盖它。一旦所有这些都完成并成功,您可以将新的 JWT 返回给客户端,然后客户端现在应该在对服务器进行任何其他经过身份验证的调用时使用该新的 JWT,以维护与服务器的经过身份验证的交互。此逻辑流程将使用户保持登录状态,因为客户端在调用刷新逻辑之前将拥有一个有效的 JWT,而在调用刷新逻辑之后它将拥有一个有效的 JWT。只有当用户不再能够提供与数据库中的用户关联的有效访问令牌时,用户才应被识别为未登录且未通过身份验证。

就 cookie 而言,无论您使用哪种方法来维护客户端上的 cookie,都应该用于设置刷新的访问令牌,就像设置您在登录时收到的初始访问令牌一样。如果服务器在将来的某个时间发现访问令牌不再有效,例如,如果您的客户端在登录后直到访问令牌过期后的某个时间才使用,那么客户端应识别服务器响应,指示此在这种情况下,再次向用户显示客户端上的登录流程,以便可以获取新的访问令牌并将其存储在客户端上的 cookie 中。

我不会担心撤销 JWT,而是让它们过期,如果发现 JWT 已过期,则启动新的登录流程。此外,我建议不要使用本地存储,而是使用会话存储来存储您的 JWT,以便您在网站上的用户会话期间拥有它,并在浏览器关闭后立即将其删除。这将防止 JWT 在会话之外持续存在,并且应该减轻您对在会话存储中保存敏感数据的担忧。此外,在生成 JWT 时,您还应该注意不要在其中存储任何敏感数据,因为 JWT 很容易进行逆向工程。这也可以防止任何类型的敏感数据暴露在客户端上。

编辑:

在开发服务器 API 时要记住的关键是您应该有两种不同类别的端点。一组应未经身份验证,一组应经过身份验证。

经过身份验证的端点集不需要在请求中包含访问令牌。此类端点的一个示例是您的登录端点,它不需要访问令牌,因为它实际上会生成一个访问令牌供您以后使用。任何其他不暴露敏感或重要信息的端点都可以包含在此类端点中。

未经身份验证的端点集需要在请求中包含访问令牌,如果未检测到访问令牌或无效的访问令牌,端点将响应 401 HTTP response code(表示未经授权的请求)。此类端点的一个示例是允许用户更新其个人信息的端点。显然,如果用户不能提供凭据来证明他们是他们正在尝试更新其信息的用户,则他们无法更新他们自己的信息。如果客户端收到带有 401 响应代码的响应,这将是客户端需要的信号,以便告诉用户重新登录以便可以检索新的有效访问令牌。如果客户端被编程为定期检查客户端上当前持有的 JWT 的到期时间并启动访问令牌刷新,则客户端可以避免这种可能性,但显然您仍然应该有逻辑来检测和响应一个 401 响应代码,以便正确管理客户端用户流。

【讨论】:

感谢您的努力,它真的对我帮助很大,我已经做了一个总结,我会重新阅读您所说的一切,以确保我不会错过任何东西,请告诉如果我在某个地方错了。我知道我错过了摘要中的细节,但希望这是流程。 用户登录时-访问令牌存储在附加到他的帐户令牌刷新路由的数据库中-在每个新用户请求时,该路由接受仍然有效的访问 jwt 令牌-验证它是否与用户匹配在数据库中 - 应该在登录路由上使用相同的逻辑生成一个新令牌并返回一个更新过期的令牌 - 服务器应该为用户覆盖数据库中的访问令牌数据,替换旧的 - 如果令牌过期服务器会告诉浏览器注销并要求用户再次登录 - 使用会话存储来存储 jwt 令牌 您总结的流程看起来不错,并且您已经涵盖了所有要点。在阅读了您的 cmets 之后,我想确保我提到了有关您的服务器 API 设计的另一件事。有关这些详细信息,请参阅我上面的编辑。 非常感谢您的宝贵时间,现在一切都清楚了。还有一个简短的最后一个问题:如果有角色(例如:管理员),是否需要第二层安全性?像第二层代币系统?或者在 JWT 中添加角色密钥并验证将完成这项工作?或者它可能需要一个完全不同的系统。 与您之前提出的问题一样,根据您的应用程序需求,有不同的方法可以实现系统的这一部分。如果您希望数据库管理权限以便您无需在应用程序逻辑中担心它,您可以在数据库中使用基于角色的安全性。或者,如果您想对应用程序进行更多控制,您可以在应用程序代码中定义角色集,并在 API 调用时检查应用程序逻辑中的角色。两者我都做了,但我建议您开始在 JWT 中添加一个“角色”字段,您可以在每次 API 调用期间检查该字段。

以上是关于使用 JWT 令牌时应该如何处理 RESTful 身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

JWT:当用户打开新标签时如何处理 GET 请求?

JWT C# Token - 如何处理或设置无限到期时间

如何处理 React Spa 上的刷新令牌?

在 Spring Boot 中验证 JWT 时如何处理 InvalidSignatureException?

收集结果的 RESTful API 中应该如何处理异常?

我应该如何处理 RESTful API 中的对象层次结构?