在 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-client 作为隐式授权类型时解决 [authorization_request_not_found]