在 Oauth2 隐式授权流和第 3 方 cookie 中刷新令牌

Posted

技术标签:

【中文标题】在 Oauth2 隐式授权流和第 3 方 cookie 中刷新令牌【英文标题】:Refreshing token in Oauth2 Implicit Grant Flow and 3rd party cookies 【发布时间】:2019-07-15 07:03:42 【问题描述】:

我想知道如何处理 Oauth2 隐式授权中的刷新令牌 2019 年主流浏览器默认禁用第 3 方 cookie 时的流量。

一些细节:

当前设置:

ui.example.com下的UI SPA应用

uaa.api.example.com 下的身份提供程序(CloudFoundry 的 UAA)

场景:

当用户登录时,身份提供者为域 uaa.api.example.com 设置包含用户详细信息的 cookie,并在重定向的 Location 标头中返回 JWT。

JWT存储在本地存储中(ui.example.com),但有效期只有1小时,所以我想刷新一下。

可以通过发送到 IDP 授权端点的 prompt=none 查询参数进行刷新(过程在 Auth0 guide (it's not UAA but flow is the same 中有详细描述)

在每 20m 隐藏 iframe 中创建 src 设置为 uaa.api.exmaple.com/oauth/authorize?prompt=none 的内容,无需用户提供凭据即可启动登录过程。当流程结束时,响应中返回的新 JWT 会再次存储在本地存储中。

问题:

当允许第三方 cookie 时,浏览器会将 IDP 的 cookie 添加到 iframe 发出的请求中,因此流程可以正常工作,并且我会在响应中获得新令牌。

当在浏览器设置中禁用第三方 Cookie 时,iframe 无法访问其自己的 Cookie,因此不会返回新的 JWT,而是返回错误 login_required。无法通过 iframe 访问 cookie 导致无法使用令牌更新

问题:

对于我的 3rd 方 cookie 问题有什么解决方案吗?

如果没有,我可以使用隐式授权流程和 SPA 的替代方法来登录和刷新令牌吗?

【问题讨论】:

【参考方案1】:

作为托管在不同域中的应用程序和身份服务器。这意味着您的应用程序正在执行跨域身份验证。跨域认证是通过第三方cookie实现的,禁用第三方cookie会导致跨域认证失败。

回答您的问题:

对于我的 3rd 方 cookie 问题有什么解决方案吗?

将您的应用程序和身份服务器托管在同一服务器下 领域。在这种情况下,您可以使用子域。

如果没有,我可以使用隐式授权流程和 SPA 的替代方法来登录和刷新令牌吗?

没有

解决方案:

我不熟悉 CloudFoundry。不确定他们是否支持。您可以通过在身份提供者端启用自定义域来解决此问题。因此,您的应用程序和身份提供者都将在同一个域中,并且 cookie 将被视为第一方。例如,将您的应用程序托管在https://acme.com 并将您的身份提供者自定义域设置为https://login.acme.com

【讨论】:

【参考方案2】:

问题:

对于我的 3rd 方 cookie 问题有什么解决方案吗?

如果您在应用和 IDP 之间使用相同的***域,那么在禁用 3rd 方 cookie 时应该没有问题。 This link 还详细说明了使用跨域策略取得的成败参半。

如果没有,是否有隐式授权流程和 SPA 的替代方案 我可以用来登录和刷新令牌吗?

我以前没有使用过 CloudFoundry,但大多数大型 OAuth2.0 提供商都提供公共客户端功能,其中公共客户端(例如您的 SPA)不需要客户端密码来获取访问/刷新令牌。这允许公共客户端使用Authorisation Code Grant,这可以允许通过刷新令牌刷新令牌,从而避免使用 HTTP 重定向和 cookie 的 silent auth 技术。

【讨论】:

【参考方案3】:

问题的根源在于iframe和隐式授权类型的使用。

我认为您使用 iframe 的原因是为了跨域访问 cookie。现在,避免使用iframe 的最简单方法是将cookie 的域设置为Domain=example.com,并在example.com 上同时拥有UI 和授权服务器。如果由于某种原因,您不能这样做,则需要采用以下方法。


推荐选项

隐式授权类型不安全。虽然这个问题不是关于授权类型的利弊,但为了给我要解释的选项设置背景,让我简单列举一下我说隐式流不安全的原因:

    缺少通过提供客户端密码和授权码的客户端身份验证步骤。所以安全性较低 访问令牌作为 URL 片段发回(这样令牌不会发送到服务器),该片段将继续保留在浏览器历史记录中 如果发生 XSS 攻击,恶意脚本可以很好地将令牌发送到攻击者控制的远程服务器

因此,推荐的选项是使用授权码授权类型。在 SPA(单页应用程序)中不使用授权码的原因之一是,它要求将客户端密码存储在浏览器中,我们知道浏览器无法保密。通过在服务器端使用代理组件(可以嵌入到资源服务器中)来保存客户端机密并充当 SPA 和授权服务器之间的代理,可以很容易地降低这种风险。

这里(在授权代码授予类型中)流程如下所示:

    用户点击 SPA 登陆页面上的登录按钮 用户被重定向到授权服务器登录页面。客户端 ID 在 URL 查询参数中提供

    用户输入他/她的凭据并单击登录按钮。用户名和密码将使用 HTTP POST 发送到授权服务器。凭据应在请求正文或标头中发送,而不是在 URL 中(因为 URL 记录在浏览器历史记录和应用程序服务器中)。此外,应设置正确的缓存 HTTP 标头,以便不缓存凭据:Cache-Control: no-cache, no-store, Pragma: no-cache, Expires: 0

    授权服务器根据用户数据库(例如 LDAP 服务器)对用户进行身份验证,其中用户名和用户密码的哈希(哈希算法,如 Argon2、PBKDF2、Bcrypt 或 Scrypt)与随机盐一起存储

    认证成功后,授权服务器将根据 URL 查询参数中提供的客户端 ID 从其数据库中检索重定向 URL。重定向 URL 是资源服务器 URL 然后用户将被重定向到资源服务器端点,并在 URL 查询参数中使用授权代码 然后资源服务器将向授权服务器发出 HTTP POST 请求以获取访问令牌。授权码、客户端 ID、客户端密码应该放在请求正文中。 (应使用上述适当的缓存标头) 授权服务器将在响应正文或标头中返回访问令牌和刷新令牌(使用上述适当的缓存标头) 资源服务器现在将通过将域属性设置为Domain=example.com 的适当cookie 将用户(HTTP 响应代码302)重定向到SPA URL(假设资源服务器和UI 都在@987654326 的子域上@)。授权服务器的域无关紧要,因为它没有设置任何 cookie。

同样,访问令牌刷新的请求可以发送到代理组件,代理组件将从cookie中读取刷新和访问令牌,并使用提取的令牌、客户端ID和访问权限调用授权服务器api客户端密码。

【讨论】:

【参考方案4】:

最后,我们决定采用不同的解决方案。当 JWT 生命周期结束时,我们会显示一个通知会话已超时的模式,并带有 2 个按钮,一个用于注销,一个用于保留会话。当用户点击“保持会话”时,将打开新的选项卡/弹出窗口,用户在 IDP 中通过再次提供其凭据或在 IDP 会话仍处于活动状态时自动进行重新身份验证。

所以流程是:

JWT lifetime ends -> 'keep session' in modal chose -> open new tab/popup-window with IDP login form -> successfully authenticated -> redirect back to app -> store token in browser's storage -> close popup-window/tab with window.close() -> get new token from storage and use it in next calls

因为我们使用新的弹出窗口/标签来重新认证,所以 3rd 方 cookie 没有问题。

这也带来了一个巨大的优势。用户无论何时返回应用程序都不会丢失他的工作,因为模态将在那里等待。我想,另外它让我们认识了Re-authenticing accessibility success criterion (level AAA)

成功标准 2.2.5 重新认证

当经过身份验证的会话到期时,用户可以在重新进行身份验证后继续进行活动而不会丢失数据。

【讨论】:

以上是关于在 Oauth2 隐式授权流和第 3 方 cookie 中刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

从 Oauth2 DocuSign API 隐式授权获取用户信息

如何获取不和谐 oauth2 隐式授权的范围值?

OAuth 2 中隐式授权类型的目的是啥?

使用 oauth2-client 作为隐式授权类型时解决 [authorization_request_not_found]

SPA 如何在 OAuth2 隐式流中提取访问令牌

Spring Security OAuth2 Demo —— 密码模式(Password)