如何保护刷新令牌?

How to secure a refresh token?

我正在使用 JWT 对我的应用程序的用户进行身份验证。当用户登录时,他们将获得访问令牌和刷新令牌。为了保证刷新令牌的安全,我没有将其存储在客户端,而是将其与他们的帐户一起保存在后端,因此不容易访问。不过,我对刷新令牌的安全性感到困惑,这是我在阅读有关如何使用刷新令牌的在线资源时理解的逻辑:

  1. 验证
  2. 在某处存储访问令牌 + 刷新令牌(在我的例子中,前端的访问令牌和后端的刷新令牌)
  3. 执行 api 请求时,在 api 端验证访问令牌
  4. 如果access token过期,使用refresh token生成新的access token+新的refresh token,将access token回传给客户端
  5. 像以前一样存储令牌...并重复

我担心的安全问题是,如果其他人(黑客)获得了访问令牌并用它向 api 发送请求,如果令牌过期 api 将使用刷新令牌获取新的访问令牌 + 新的刷新令牌并且 return 至少将访问令牌提供给黑客。

我读了几次 this article about 5-6 times and I read this 文章,以及其他一些关于这个主题的文章,他们都说了

make sure to store the refresh token securely because it's long lived, the access_token is short lived so not as big of a deal

但根据我上面描述的流程,访问令牌是否短暂并不重要,刷新令牌将用于获取新的访问令牌并永久访问。

有什么我想念的吗?如果黑客获得了过期的访问令牌,api 将如何知道是谁在发送请求?它仍然会使用刷新令牌发送一个新的。我是否应该以某种方式验证发送请求的人?


更新

所以我明白,当请求新的访问令牌时,我需要发送刷新令牌、客户端 ID 和客户端密码。我遇到的问题是,就像以前一样,黑客可以向我的 API 服务器发送请求,服务器从黑客那里获取被劫持的访问令牌,它会看到它已过期,因此它会发送刷新令牌,连同 clientID/client 秘密(存储为环境变量)到 Auth API 并取回新的访问令牌/刷新令牌,这让我们回到了同样的问题。


更新 2

关于这个主题的一些有趣的问题:

  1. Why Does OAuth v2 Have Both Access and Refresh Tokens?
  2. https://security.stackexchange.com/questions/87119/how-secure-are-expiring-tokens-and-refresh-tokens

根据第二个问题和答案,刷新令牌似乎不是一种更安全的维护访问方式,只是它更容易检测到黑客,因为 auth/refresh 令牌不断被请求和失效对方的代币。这样做的问题是,只有当 2 个用户同时尝试访问资源时才会发生这种情况 - 如果只有黑客恰好在给定时间段处于活动状态,他将可以无限制地访问原始用户数据,直到原始用户尝试使用应用程序和访问受保护的资源

您不应将令牌存储在服务器上。客户端进行身份验证并获取令牌。您将令牌存储在浏览器中的 cookie 或 localStorage 中。每个请求都使用令牌授权。如果您在没有 ssl 的情况下通过未加密的通道发送它,它很容易被拦截。获得令牌的黑客确实允许他们冒充用户。过期的令牌不应允许在不重新输入用户凭据的情况下重新进行身份验证。应忽略过期的令牌。

在您链接的第二篇文章中,据说要刷新令牌,您必须 post 刷新令牌 client_id client_secret 所以基本上你在刷新访问令牌时重新验证用户。

To use the refresh token, make a POST request to the service’s token endpoint with grant_type=refresh_token, and include the refresh token as well as the client credentials.

如果您将刷新令牌存储在服务器上,您的服务器应在身份验证响应中包含一个安全会话 cookie 以识别用户。您可以通过使用 HttpOnly 标志设置 cookie 来防止攻击者提取安全会话 cookie。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies

该 cookie 不是刷新令牌。它将是某种其他类型的会话 cookie。一种不向用户返回刷新令牌的应用程序流程是单页应用程序流程。

https://auth0.com/docs/flows/concepts/single-page-login-flow

在这个流程中,令牌刷新是通过"Silent Authentication."

完成的

https://auth0.com/docs/api-auth/tutorials/silent-authentication#initiate-a-silent-authentication-request

A successful authentication response if the user already has a valid session in Auth0 and no consent or other prompts are needed.

所以我们需要通过存储一些用户标识符来维护会话。

访问令牌和刷新令牌的用途如下:

  1. 在用户登录时生成过期访问和刷新令牌并发送到前端应用程序(Android、IOS、Web 应用程序)。
  2. 前端应用程序安全地将刷新令牌存储在其数据库中。
  3. 前端应用随每个请求发送访问令牌,JWT 对其进行验证 没有访问数据库。
  4. 身份验证在定义的访问令牌时间有效。
  5. 到期时,前端应用程序会向您的服务器发送刷新令牌, 此外,您还使用 JWT 验证它并在数据库中检查它 为了平等。
  6. 服务器生成新的访问令牌等。

PS: Whole communication should take place over HTTPS.

我的实现基于上述逻辑,访问令牌每 30 分钟过期一次,刷新令牌的有效期为一年。

此外,使用数据库验证刷新令牌的事情是您可以控制用户登录过程,并且可以限制能够使用同一帐户使用您的应用程序的设备数量。

只要用户再次发送登录请求,您只需更新服务器上的刷新令牌即可。

有一篇很好的文档 OAuth 2.0 for Browser-Based Apps,其中讨论了这些应用程序的最佳实践。

我会选择在客户端或服务器上保留令牌。混合使用它(在服务器上保留刷新令牌并在浏览器中保留访问令牌),您可以创建自己的协议,但它有自己的漏洞。

如果浏览器应用程序需要访问令牌只是为了访问其后端,您可以考虑将您的后端用作 OAuth2 客户端(接收授权代码),获取用户的身份,发出一个 cookie 来维护会话在浏览器和后端之间。它比交换、刷新和验证 OAuth2 令牌容易得多。

如果您真的想将浏览器应用程序保留为接收令牌的 OAuth2 客户端,您应该使用 PKCE extension (so the auth code kept in network caches and browser history cannot be used to get tokens) and get a new refresh token with each new access token - take a look at the chapter about refresh tokens:

Authorization servers SHOULD NOT issue refresh tokens to browser-based applications.

If an authorization server does choose to issue refresh tokens to browser-based applications, then it MUST issue a new refresh token with every access token refresh response. Doing this mitigates the risk of a leaked refresh token, as a leaked refresh token can be detected if both the attacker and the legitimate client attempt to use the same refresh token.

您的浏览器应用程序可以将其令牌保留在 sessionStorage 中以防止页面重新加载。

TL;DR(改善用户体验的选项)

  1. 存储短期访问令牌in-memory 或本地存储。
  2. 商店刷新令牌in-memory(仅)

现在,只要用户不执行硬刷新或导航离开您的站点,他们就可以永远登录。


一般想法

我不是不同代币的所有复杂性、它们的机制和它们的存储最佳实践的专家(所以请参考其他 articles/experts 关于这个问题 - Tim Hardy 提出了一个极好的和强有力的反驳根据我在下面评论中的发现),但在基于浏览器的应用程序中使用刷新令牌似乎不是一个好主意。刷新令牌可以安全地存储在 phones/other 台设备上。您可能会在浏览器中使用仅 http cookie 或将令牌存储在内存中(请参阅下文),但同样,我不确定这种方法的安全性(我不是说它不安全,我是说我不知道有多安全)


改善用户体验

虽然(再次)我个人不知道在浏览器存储中存储 access/refresh 令牌所涉及的全部风险(除了 XSS 攻击), 这里有几个相对安全的选择。

要么仅存储 long-lived 访问令牌 in-memory,要么使用 short-lived 访问令牌的组合(in-memory 或 localstorage 也可能是安全的,因为它的 short-lived) 仅加上一个刷新标记 in-memory。这种方法允许用户永远登录,只要他们不刷新页面或离开即可。

刷新令牌的安全性取决于授权服务器如何实施识别客户端、重新生成刷新令牌和使刷新令牌失效的策略。

根据安全策略(对于web客户端),刷新令牌可以在请求新的访问令牌时失效,并且授权服务器也生成新的刷新令牌并返回给客户端。 https://www.rfc-editor.org/rfc/rfc6749#section-10.4

从这个问题中我感觉到,授权服务器和资源服务器被视为一个,但事实并非如此。授权服务器是生成访问令牌 and/or 刷新令牌的服务器,而资源服务器是具有客户端尝试访问的资源的服务器。此外,刷新令牌仅提供给授权服务器以获取新的访问令牌,而访问令牌由客户端提供给资源服务器以获得对受保护资源的访问权限。