我不知道我是否只是有某种盲点或什么,但我已经多次阅读 OAuth 2 规范并仔细阅读邮件列表档案,我还没有找到一个很好的解释来解释为什么隐式授权已经开发了获取访问令牌的流程。与授权码授予相比,它似乎只是无缘无故地放弃了客户端身份验证。这是如何“针对使用脚本语言在浏览器中实现的客户端进行优化”(引用规范)?
两个流程开始时相同(来源:https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-22):
客户端通过将资源所有者的用户代理定向到授权端点来启动流程。授权服务器验证资源所有者(通过用户代理)并确定资源所有者是允许还是拒绝客户端的访问请求。假设资源所有者授予访问权限,授权服务器使用之前提供的重定向 URI(在请求中或在客户端注册期间)将用户代理重定向回客户端。
重定向 URI 包含授权码(授权码流)
重定向 URI 在 URI 片段中包含访问令牌(隐式流)
这是流量分裂的地方。在这两种情况下,此时的重定向 URI 都指向客户端托管的某个端点:
在授权代码流中,当用户代理使用 URI 中的授权代码访问该端点时,该端点上的代码将授权代码与其客户端凭据一起交换为访问令牌,然后它可以根据需要使用该令牌。例如,它可以将其写入网页上的脚本可以访问的网页。
隐式流程完全跳过了这个客户端身份验证步骤,只是加载了一个带有客户端脚本的网页。 URL 片段有一个可爱的技巧,可以防止访问令牌被过多地传递,但最终结果基本相同:客户端托管的站点提供一个页面,其中包含一些可以获取访问令牌的脚本.
因此我的问题是:跳过客户端身份验证步骤在这里获得了什么?
以下是我的想法:
授权码流中授权码 + 令牌的目的是令牌和客户端密码永远不会暴露给资源所有者,因为它们在服务器到服务器之间传输。
另一方面,隐式授权流适用于完全使用 javascript 实现并在资源所有者的浏览器中运行的客户端。您不需要任何服务器端代码即可使用此流程。然后,如果一切都发生在资源所有者的浏览器中,那么发布验证码和客户端密码就没有意义了,因为令牌和客户端密码仍将与资源所有者共享。包括身份验证代码和客户端密码只会使流程更加复杂,而不会增加任何真正的安全性。
那么关于“获得了什么?”的答案是什么?是“简单”。
它的存在是出于安全原因,而不是为了简单。
您应该考虑用户代理和客户端之间的区别:
用户代理是用户(“资源所有者”)与系统的其他部分(身份验证服务器和资源服务器)进行通信的软件。
客户端是要访问资源服务器上用户资源的软件。
在用户代理和客户端分离的情况下,授权代码授予是有意义的。例如,用户使用网络浏览器(用户代理)在 Kickstarter 上使用他的 Facebook 帐户登录。在这种情况下,客户端是 Kickstarter 的服务器之一,它处理用户登录。此服务器从 Facebook 获取访问令牌和刷新令牌。因此这种类型的客户端被认为是“安全的”,由于访问受限,令牌可以被保存,Kickstarter 可以访问用户的资源,甚至可以在没有用户交互的情况下刷新访问令牌。
如果用户代理和客户端是耦合的(例如本地移动应用程序、javascript 应用程序),则可以应用隐式授权工作流。它依赖于资源所有者的存在(用于输入凭据)并且不支持刷新令牌。如果此客户端存储访问令牌以供以后使用,这将是一个安全问题,因为该令牌可以很容易地被客户端的其他应用程序或用户提取。没有刷新令牌是一个额外的提示,即此方法不是为在用户不在时访问用户资源而设计的。
通常的解释是,当您使用 JavaScript 客户端时,隐式授权更容易实现。但我认为这是错误的看待它的方式。如果您使用的 JavaScript 客户端直接通过 XMLHttpRequest 请求受保护的资源,则隐式授权是您唯一的选择,尽管它的安全性较低。*
授权码授予提供了额外的安全性,但它仅在您拥有请求受保护资源的 Web 服务器时才有效。由于 Web 服务器可以存储访问令牌,因此访问令牌暴露于 Internet 的风险较小,并且您可以发出一个持续很长时间的令牌。并且由于 Web 服务器是受信任的,因此可以给它一个“刷新令牌”,这样它就可以在旧的访问令牌过期时获得一个新的访问令牌。
但是——这是很容易忽略的一点——授权代码流的安全性只有在 Web 服务器受到会话保护的情况下才有效,会话是通过用户身份验证(登录)建立的。如果没有会话,不受信任的用户只能使用 client_id 向 Web 服务器发出请求,这就像用户拥有访问令牌一样。添加会话意味着只有经过身份验证的用户才能访问受保护的资源。 client_id 只是 JS webapp 的“身份”,而不是所述 webapp 的身份验证。
这也意味着您可以在 OAuth 令牌过期之前结束会话。没有使访问令牌无效的标准方法。但是如果你的会话过期了,访问令牌就没有用了,因为除了 Web 服务器之外没有人知道它。如果不受信任的用户获得了对您会话密钥的访问权,那么只要会话有效,他们就只能访问受保护的资源。
如果没有 Web 服务器,则必须使用隐式授权。但这意味着访问令牌会暴露在 Internet 上。如果不受信任的用户可以访问它,他们可以使用它直到它过期。这意味着他们可以访问它的时间比授权码授予的时间长。因此,您可能需要考虑让令牌更快过期,并避免授予对更敏感资源的访问权限。
*编辑:最近,人们建议您避免使用隐式授权,即使在没有服务器的 Web 应用程序上也是如此。相反,您可以使用配置了空密钥的授权代码授权以及 PKCE。 auth-code grant 避免了将访问令牌存储在浏览器历史记录中,并且 PKCE 避免在有人劫持重定向 URL 以窃取 auth 代码时暴露它。在这种情况下,您需要服务器避免返回刷新令牌,因为您的客户端可能无法安全地存储它。它应该发出具有上述相同限制的访问令牌。
归结为:如果用户正在运行基于浏览器的或“公共”(JavaScript) Web 应用程序而没有服务器端组件,则用户隐式信任该应用程序(及其运行所在的浏览器,可能与其他浏览器一起使用)基于应用程序...)。
没有第三方远程服务器,只有资源服务器。授权码没有任何好处,因为除了浏览器之外没有其他代理代表用户。出于同样的原因,客户端凭据没有任何好处。 (任何客户端都可以尝试使用此流程。)
然而,安全影响是显着的。从 https://www.rfc-editor.org/rfc/rfc6749#section-10.3:
使用隐式授权类型时,访问令牌在 URI 片段中传输,这可能会将其暴露给未授权方。
从 https://www.rfc-editor.org/rfc/rfc6749#section-10.16:
资源所有者可以通过向攻击者的恶意客户端授予访问令牌来自愿委托对资源的访问。这可能是由于网络钓鱼或其他一些借口...
我不确定我是否正确理解了答案和丹的评论。在我看来,答案已经说明了一些正确的事实,但它确实指出了 OP 的要求。如果我理解正确,隐式授权流的主要优点是像 JS 应用程序(例如 Chrome 扩展)这样的客户端不必公开客户端密码。
丹·塔夫林 说:
...在授权代码流中,资源所有者永远不需要查看访问令牌,而在 javascript 客户端中,这是不可避免的。但是,仍然可以使用授权代码流向 javascript 客户端保留客户端机密。
也许我误解了你,但是客户端(在这种情况下是 JS 应用程序)必须在授权代码流中将客户端凭据(客户端密钥和秘密)传递给资源服务器,对吧?客户端机密不能“对 JS 保密”。
虽然 Implicit Grant 旨在支持无法保护客户端密码的应用程序(包括客户端 JavaScript 应用程序),但一些提供商正在实施替代方案,使用授权代码而不使用客户端密码。 OAuth 2.0 IETF RFC-6749 于 2012 年发布,目前一些最近讨论的建议来自 2017 年。
2017 年关于 IETF OAuth 邮件列表的讨论可从以下实施者处获得:
红帽:https://www.ietf.org/.../oauth/current/msg16966.html
德国电信:https://www.ietf.org/.../oauth/current/msg16968.html
智能健康 IT:https://www.ietf.org/.../oauth/current/msg16967.html
在这里阅读更多:
https://aaronparecki.com/oauth-2-simplified/
https://aaronparecki.com/oauth-2-simplified/#single-page-apps
以前建议没有秘密的客户使用隐式,但已被使用无秘密的授权代码授予所取代。 ... 以前,建议基于浏览器的应用程序使用“隐式”流程,该流程立即返回访问令牌并且没有令牌交换步骤。自最初编写规范以来,行业最佳实践已更改为建议在没有客户端密码的情况下使用授权代码流。这为创建安全流提供了更多机会,例如使用 state 参数。参考资料:Redhat、德国电信、Smart Health IT。
此处还提到了移动应用程序从隐式授权迁移到没有客户端密钥的验证代码:
https://aaronparecki.com/oauth-2-simplified/#mobile-apps
在隐式流程中,如果用户的浏览器损坏(恶意扩展/病毒),则损坏会访问用户的资源并可能做坏事。
在身份验证流程中,损坏不能,因为它不知道客户端密码。
除了其他答案之外,同样重要的是要认识到隐式配置文件只允许前端通道流,而不是需要回调授权服务器的授权代码流;这在 OpenID Connect 中变得很明显,它是一个建立在 Auth 2.0 之上的 SSO 协议,其中隐式流类似于非常流行的 SAML POST 绑定,而授权代码流类似于不太广泛部署的 SAML Artifact 绑定
https://www.rfc-editor.org/rfc/rfc6749#page-8
隐式 隐式授权是一种简化的授权代码流,针对使用 JavaScript 等脚本语言在浏览器中实现的客户端进行了优化。在隐式流程中,不是向客户端颁发授权代码,而是直接向客户端颁发访问令牌(作为资源所有者授权的结果)。授权类型是隐式的,因为没有颁发中间凭证(例如授权代码)(稍后用于获取访问令牌)。在隐式授权流程期间发布访问令牌时,授权服务器不会对客户端进行身份验证。在某些情况下,可以通过用于将访问令牌传递给客户端的重定向 URI 来验证客户端身份。访问令牌可能会暴露给资源所有者或其他有权访问资源所有者的用户代理的应用程序。隐式授权提高了某些客户端(例如作为浏览器内应用程序实现的客户端)的响应能力和效率,因为它减少了获取访问令牌所需的往返次数。
我认为 Will Cain 回答了这个问题,他说“出于同样的原因,客户端凭据没有任何好处。(任何客户端都可以尝试使用此流程。)”还考虑到隐式流程的 redirect_uri 可能是“localhost”--没有回调由授权服务器为隐式流创建。由于无法预先信任客户端,因此用户必须批准发布用户声明。
隐式授权允许使用 GET
从 Authorization Endpoint 获取令牌。这意味着授权服务器不必支持 CORS。
如果这不是问题,并且没有其他与授权服务器不灵活相关的问题(例如,出于某种原因,刷新令牌不是可选的),那么授权代码流是首选,即使对于公共客户端也是如此,根据 recent industry trends并且至少到这个(当前)instance of an official draft。
从历史上看,实现隐式流程还有其他原因,但目前授权代码授予提供的安全优势似乎超过了这些原因,包括:
通过反向渠道为机密客户提供和使用代币的选项
不在公共客户端的浏览器历史记录中公开令牌
在颁发令牌之前中断未经授权的流程 - 使用 PKCE,用于“各种 OAuth 客户端”
我刚刚看到一些关于 OAuth 2.0 的文章。作者指出,隐式流程背后的原因是 JS 应用程序在请求中受到非常严格的限制:
如果你想知道为什么 OAuth 2.0 中包含隐式类型,解释很简单:同源策略。当时,不允许前端应用程序向不同的主机发送请求以使用代码获取访问令牌。今天我们有CORS(跨域资源共享)。
Auth code
与client_id
和client_secret
一起用于识别可以刷新令牌以进行长时间登录和 "offline login" 的可信客户端。但是在客户端应用程序中,无法注册每个客户端,因此“简化”隐式授权类型用于临时访问用户信息