如何保护刷新令牌?
Posted
技术标签:
【中文标题】如何保护刷新令牌?【英文标题】:How to secure a refresh token? 【发布时间】:2019-08-24 11:05:47 【问题描述】:我正在使用 JWT 对我的应用的用户进行身份验证。当用户登录时,他们会获得一个访问令牌和一个刷新令牌。为了保证刷新令牌的安全,我不将其存储在客户端,而是将其与他们的帐户一起保存在后端,因此不容易访问。不过,我对刷新令牌的安全性感到困惑,这是我在阅读有关如何使用刷新令牌的在线资源时所理解的逻辑:
-
认证
在某处存储访问令牌 + 刷新令牌(在我的情况下,前端的访问令牌和后端的刷新令牌)
执行api请求时,在api端验证访问令牌
如果访问令牌过期,使用刷新令牌生成新的访问令牌+新的刷新令牌,将访问令牌发送回客户端
像以前一样存储令牌...并重复
我担心的安全问题是,如果其他人(黑客)持有访问令牌并使用它向 api 发送请求,如果令牌过期,api 将使用刷新令牌来获取一个新的访问令牌 + 新的刷新令牌,并至少将访问令牌返回给黑客。
我读了this的文章大约5-6次,我读了几遍this的文章,以及其他一些关于这个主题的文章,他们都说类似
确保安全地存储刷新令牌,因为它是长期存在的,access_token 是短暂的,所以没什么大不了的
但是根据我上面描述的流程,访问令牌是否短暂无关紧要,刷新令牌将用于获取新的访问令牌并永久访问。
我有什么遗漏吗?如果黑客获得了过期的访问令牌,api 如何知道谁在发送请求?它仍然会使用刷新令牌发送一个新令牌。我应该以某种方式验证谁在发送请求吗?
更新
所以我明白,当请求新的访问令牌时,我需要发送刷新令牌、客户端 ID 和客户端密码。我遇到的问题是,像以前一样,黑客可以向我的 API 服务器发送请求,服务器从黑客那里获取被劫持的访问令牌,它会看到它已经过期,所以它会发送刷新令牌,以及将 clientID/client 密钥(存储为环境变量)传递给 Auth API 并取回新的访问令牌/刷新令牌,这使我们回到了同样的问题。
更新 2
关于这个主题的一些有趣的问题:
-
Why Does OAuth v2 Have Both Access and Refresh Tokens?
https://security.stackexchange.com/questions/87119/how-secure-are-expiring-tokens-and-refresh-tokens
根据第二个问题和答案,刷新令牌似乎不是维护访问的更安全方式,只是更容易检测到黑客,因为身份验证/刷新令牌不断被请求并使其他令牌无效。问题在于,只有当 2 个用户同时尝试访问资源时才会发生这种情况 - 如果只有黑客恰好在给定时间段内处于活动状态,他将可以无限制地访问原始用户数据,直到原始用户尝试使用应用程序并访问受保护的资源
【问题讨论】:
【参考方案1】:对于不同令牌的所有复杂性、它们的机制和存储最佳实践,我不是专家(因此请参阅有关该主题的其他文章/专家 - Tim Hardy 对我在下面的 cmets),但在基于浏览器的应用程序中使用刷新令牌似乎是个坏主意。刷新令牌可以安全地存储在手机/其他设备上。您可能会在浏览器中使用仅 http cookie 或将令牌存储在内存中(请参阅下文),但同样,我不确定这种方法的安全性(我不是说它不安全,我是说我不知道有多安全)
可以对此答案进行改进。虽然我个人不知道在浏览器存储中存储访问/刷新令牌所涉及的全部风险,但本地存储中的短暂(
【讨论】:
我有完全相同的问题,似乎没有就刷新令牌应该做什么达成一致。如果您同时拥有 SPA 网络应用和原生移动应用,您将如何实施身份验证以服务于这两个平台? 您将在移动应用上使用刷新令牌,而在 SPA 上不使用刷新令牌。至少,我会这样做 据我了解,刷新令牌是否应该在客户端以刷新访问令牌?否则,如何刷新访问令牌?而且它也是一种无效机制,因此用户的密码已更改,然后发出新的刷新令牌,因此旧的不再有效。我同意很多讨论似乎以不同的方式进行,并且没有真正的解决方案。这是我发现的一篇关于在客户端存储刷新令牌的文章:hasura.io/blog/best-practices-of-using-jwt-with-graphql/… 我不同意。刷新令牌可以在服务器上失效,它们也可以与 DeviceIds 一起使用,保存在 httponly cookie 中。如果对新访问令牌的请求带有刷新令牌但不是正确的 DeviceId,则服务器可以拒绝它。这是当今许多身份验证系统的基本机制。这就是为什么您会在很多系统上看到“我看到您正在从新设备登录”消息,而且效果很好。 @TimHardy 有趣的是,OIDC 中没有提到这些机制。【参考方案2】:有一个很好的文档OAuth 2.0 for Browser-Based Apps 讨论了这些应用程序的最佳实践。
我会选择在客户端或服务器上保留令牌。混合使用(在您的服务器上保留刷新令牌并在浏览器中保存访问令牌),您可以创建自己的协议,并拥有自己的漏洞。
如果浏览器应用程序只需要访问令牌来访问其后端,您可以考虑将您的后端用作 OAuth2 客户端(接收身份验证代码),获取用户的身份,发出一个 cookie,该 cookie 将在浏览器和后端。这比交换、刷新和验证 OAuth2 令牌要容易得多。
如果您确实希望将浏览器应用程序保留为接收令牌的 OAuth2 客户端,则应使用PKCE extension(因此保存在网络缓存和浏览器历史记录中的身份验证代码不能用于获取令牌)并获得新的刷新每个新访问令牌的令牌 - 看看chapter about refresh tokens:
授权服务器不应向基于浏览器的应用程序发出刷新令牌。
如果授权服务器确实选择向基于浏览器的应用程序发出刷新令牌,那么它必须发出新的刷新 每个访问令牌刷新响应的令牌。这样做 降低了泄露刷新令牌的风险,因为泄露的刷新 如果攻击者和合法的令牌都可以被检测到 客户端尝试使用相同的刷新令牌。
您的浏览器应用程序可以将其令牌保存在 sessionStorage
中,以在页面重新加载后继续存在。
【讨论】:
【参考方案3】:您不应将令牌存储在服务器上。客户端进行身份验证并获取令牌。您将令牌存储在浏览器中的 cookie 或 localStorage 中。每个请求都使用令牌进行授权。如果您通过没有 ssl 的未加密通道发送它,它很容易被拦截。获得令牌的黑客确实允许他们冒充用户。过期的令牌不应该允许在不重新输入用户凭据的情况下重新进行身份验证。过期的令牌应该被忽略。
【讨论】:
访问令牌存储在前端/客户端。根据我链接的文章,刷新令牌不应存储在浏览器中【参考方案4】:访问令牌和刷新令牌的用途如下:
-
在用户登录时生成过期访问和刷新令牌并发送到前端应用程序(android、ios、Web 应用程序)。
前端应用将刷新令牌安全地存储在其数据库中。
前端应用程序随每个请求发送访问令牌,JWT 对其进行验证
无需访问数据库。
身份验证适用于定义的访问令牌时间。
当它过期时,前端应用程序将刷新令牌发送到您的服务器,
另外,您使用 JWT 验证它并在数据库中检查它
为了平等。
服务器生成新的访问令牌等。
PS:整个通信都应该通过 HTTPS 进行。
我的实现基于上述逻辑,访问令牌每 30 分钟到期一次,并且刷新令牌有效期为一年。
使用数据库验证刷新令牌的另一个好处是您可以控制用户登录过程,并且您可以限制能够使用相同帐户使用您的应用的设备数量。
只要用户再次发送登录请求,您只需在服务器上更新刷新令牌。
【讨论】:
这是假设 auth 和 api server 是一样的?我仍然不明白这是如何防止黑客的。假设我持有您的访问令牌,我向 api 服务器发送数据请求,jwt 验证令牌并看到它已过期,api 说“好的,让我获得一个新的访问令牌”所以它发送一个请求使用存储在数据库中的刷新令牌来自身份验证服务器的新访问令牌。它使用过期访问令牌中的用户 ID 从数据库中获取正确的刷新令牌。或者它如何从数据库中获取刷新令牌? 这是假设 auth 和 api server 是一样的?只要您在所有服务器上使用相同的密钥都没关系,是否相同的服务器无关紧要。不明白这如何防止黑客?这里 api 不会发送对新访问令牌的请求,您的应用程序会因为应用程序安全地存储了刷新令牌。如果需要更详细的解释,我可以相应地更新我的答案。另请注意,解析通过 HTTPS 发送的数据并不容易。 为了让某人获得您的访问令牌或刷新令牌,他们必须物理窃取您的设备并解密数据库(耗时)或误导您安装一些恶意软件应用程序。这并不容易,而且完成所有这些都需要时间,因此您可以根据安全性对您的重要性相应地设置令牌到期时间。 你能在这里澄清'app'和'api'之间的区别吗?你是说'api不会发送新访问令牌的请求'。但是我的客户端密钥存储在我的服务器上(即 api) - 如果客户端密钥在我的服务器上,我该如何使用它,但我的服务器不会发送请求? @AdityaT 那么,考虑到非 httpOnly cookie 或 localStorage 可以被页面上的任何 JS 脚本访问,包括第三方脚本(例如支付),那么前端 Web 应用商店如何安全地刷新令牌提供者、分析脚本等)?【参考方案5】:在您链接的第二篇文章中,据说要刷新令牌,您必须发布刷新令牌 和 client_id 和 client_secret 所以基本上你在你重新验证用户时刷新访问令牌。
要使用刷新令牌,请使用 grant_type=refresh_token 向服务的令牌端点发出 POST 请求,并包含刷新令牌以及客户端凭据。
【讨论】:
我明白了,如果我不存储客户端凭据,我将在哪里获得它们?【参考方案6】:如果您将刷新令牌存储在服务器上,您的服务器应在身份验证响应中包含一个安全会话 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
在此流程中,令牌刷新是通过“静默身份验证”完成的。
https://auth0.com/docs/api-auth/tutorials/silent-authentication#initiate-a-silent-authentication-request
一个成功的身份验证响应如果用户在 Auth0 中已经有一个有效的会话并且不需要同意或其他提示。
所以我们需要通过存储一些用户标识符来维护会话。
【讨论】:
你的意思是如果我在客户端存储刷新令牌?即 - 在浏览器中?【参考方案7】:刷新令牌的安全程度取决于授权服务器如何实施识别客户端、重新生成刷新令牌和使刷新令牌失效的策略。
根据安全策略(对于 web 客户端),刷新令牌可以在请求新访问令牌时失效,并且授权服务器也会生成新的刷新令牌并返回给客户端. https://www.rfc-editor.org/rfc/rfc6749#section-10.4
从问题中我了解到,授权服务器和资源服务器被视为一个,但事实并非如此。授权服务器是生成访问令牌和/或刷新令牌的服务器,而资源服务器是具有资源的服务器,客户端尝试访问。此外,刷新令牌仅提供给授权服务器以获取新的访问令牌,而访问令牌由客户端提供给资源服务器以获取对受保护资源的访问权限。
【讨论】:
以上是关于如何保护刷新令牌?的主要内容,如果未能解决你的问题,请参考以下文章
保护 Azure Active Directory B2C 访问令牌和刷新令牌
如何使用 Spring boot keycloak 适配器 + spring security 强制更新 oAuth 令牌(访问令牌 + 刷新令牌)。?