免责声明
如果觉得我必须从一个重要的免责声明开始这篇文章: 不要太相信我要说的话。 我之所以这样说,是因为我们正在讨论安全性。 而且, 当您谈论安全性时,除了100%正确的陈述外,还有冒任何其他风险的风险。 因此,请记住本文,牢记您的真理来源应为官方规格 ,这只是我自己脑中概述该主题并将其介绍给初学者的概述。
任务
我决定写这篇文章是因为我总是发现OAuth2令人困惑 。 即使现在我对此有所了解,我还是觉得有些困惑。
即使当我需要摆弄它们的API时,即使能够遵循Google或Pinterest之类的在线教程, 也总是感觉像是一种伏都教 ,带有所有这些代码和Bearer令牌。
每当他们提到我可以针对特定步骤做出自己的决定时(在标准OAuth2方法中进行选择),我的头脑就会变得盲目。
希望我能解决一些问题,以便从现在开始,您可以更加放心地遵循OAuth2教程。
什么是OAuth2?
让我们从定义开始:
OAuth 2是一个授权 框架 ,使应用程序能够获得对HTTP服务上用户帐户的有限访问权限 。
上面的句子是可以合理理解的 ,但是如果我们确定所选的术语,我们可以改进。
名称的Auth部分显示其本身为Authorization (可能是Authentication;不是)。
框架很容易被忽略,因为框架一词经常被滥用; 但是要保留在这里的想法是, 它不一定是最终产品或完全定义的东西 。 这是一个工具集。 想法,方法和定义明确的交互作用的集合,您可以使用这些交互作用在其之上构建内容!
它使应用程序能够获得有限的访问权限 。 这里的关键是它使应用程序不是人类 。 对用户帐户的有限访问可能是定义的关键部分,它可以帮助您记住和解释什么是OAuth2: 主要目的是允许用户委派对用户拥有的资源的访问。 将其委托给应用程序。
OAuth2与委派有关。
它是关于一个人的,它指示软件代表她做某事。
该定义还提到了受限访问权限 ,因此您可以想象能够仅委派部分功能。
最后总结到提到HTTP服务 。 授权委托发生在HTTP服务上 。
OAuth2之前的委派
既然上下文应该更加清晰,我们可以自问: 在OAuth2和类似概念出现之前,事情是如何完成的?
好吧,在大多数情况下,情况就像您猜到的一样糟糕: 有一个共享的秘密 。
如果我希望授予软件A访问服务器B上我的东西的权限,那么大多数时候,方法是将用户/密码授予软件A,以便它可以代表我使用它。 您仍然可以在许多现代软件中看到这种模式,我个人希望它会让您感到不舒服。 您知道他们在说什么: 如果您共享一个秘密,那就不再是秘密!
现在想象一下,如果您可以为需要与之共享某些服务的每个服务创建一个新的管理员/密码对。 我们称它们为临时密码 。 它们与您用于特定服务的主帐户有所不同,但是它们仍然允许访问与您相同的服务。 在这种情况下,您将可以委派,但您仍将负责跟踪需要创建的所有这些仅适用于应用程序的新帐户。
OAuth2 –想法
请记住,我们要解决的业务问题是“委托”问题,我们希望扩展临时密码的概念,以减轻用户管理这些临时密码的负担。
OAuth2调用这些临时密码令牌 。
令牌实际上还不止于此,我将尝试说明它,但是将它们与一个更简单的即席即席密码概念联系起来可能会很有用。
OAuth2 –核心业务
Oauth 2核心业务涉及:
- 如何获得代币
OAuth2 –什么是令牌?
既然一切似乎都围绕令牌,那么什么是令牌?
到目前为止,我们已经使用了即席密码的类比,这种密码对我们很有用,但也许我们可以做得更好。
如果我们在OAuth2规范中寻找答案怎么办? 好吧,准备失望。 OAuth2规范未提供有关如何定义令牌的详细信息。 为什么甚至有可能呢? 还记得我们说过OAuth2只是“一个框架”吗? 好吧,这是定义很重要的情况之一! 规范只是告诉您令牌是什么的逻辑定义,并描述了令牌需要具备的某些功能。 但最后,规范说的是令牌是字符串。 包含访问资源的凭据的字符串。 它提供了更多细节,但是可以说在大多数情况下,令牌中的内容并不重要。 只要应用程序能够使用它们。
令牌就是那种东西,它允许应用程序访问您感兴趣的资源。
为了指出如何避免过分思考令牌是什么,规范还明确指出“对客户端通常是不透明的”! 它们实际上是在告诉您甚至不需要您了解它们! 要记住的事情更少,听起来不错!
但是为了避免将其变成纯粹的哲学课,让我们展示一下令牌可能是什么
{"access_token": "363tghjkiu6trfghjuytkyen","token_type": "Bearer"
}
快速浏览一下,可以告诉我们,这是一个字符串。 类似于JSON,但这可能只是因为json最近很流行,而不一定是必需的。 我们可以发现一个部分,看起来像一个随机字符串,一个id: 363tghjkiu6trfghjuytkyen
。 程序员知道,当您看到类似这样的内容时,至少在字符串不太长的时候,这可能表明它只是一个密钥,您可以将其与存储在其他位置的更详细的信息相关联。 在这种情况下也是如此。 更具体地说,附加信息将是有关该代码所代表的特定授权的详细信息。
但是,另一件事应该引起您的注意: "token_type": "Bearer"
。
您的合理问题应该是: Bearer
令牌类型的特征是什么? 还有其他类型吗? 哪个?
幸运的是,由于我们努力使事情变得简单,答案很简单(有人可能会说,很容易造成混淆……)
规格仅谈论Bearer
令牌类型!
嗯,为什么这样设计令牌的人觉得他必须指定唯一的已知值? 您可能会在这里开始看到一种模式:因为OAuth2只是一个框架!
它建议您如何做事,并为您做出选择提供一些繁重的工作,但最后,您有责任使用框架来构建所需的内容。
我们只是说,尽管在这里我们只讨论Bearer
令牌,但这并不意味着您不能定义自定义类型,而是允许您将其归因于它。
好的,只是一种类型。 但这是一个奇怪的名字 。 名称是否暗示任何相关内容? 也许这是一个愚蠢的问题,但是对于像我这样的非英语母语人士来说, Bearer
在这种情况下的含义可能会有些混乱。
实际上,其含义很简单:
不记名令牌是指如果您有有效的令牌,我们就会信任您。 无话可问。
如此简单,令人困惑。 您可能会争辩:“好吧,现实世界中所有类似令牌的对象都是这样工作的:如果我有有效的钱,则将它们换成出售的商品”。
正确。 这是无记名令牌的有效示例。
但是,并非每个令牌都属于Bearer。 例如,机票不是持票人代币。 仅凭一张机票就不能登机了 。 您还需要显示一个有效的ID,以便您的票证可以与之匹配; 如果您的姓名与机票相符,而您的面部与身份证相符,则可以登机。
总结一下,我们正在使用一种令牌,如果您拥有其中的一个,就足以访问资源。
并且请您思考:我们说过OAuth2与授权有关。 如果您希望将令牌传递给某人进行委派,则具有此特征的令牌显然很方便。
令牌类比
再一次,这可能是我的非英语母语背景,建议我进行澄清。 当我寻找意大利语的第一笔代币翻译时,我指的是一个物理对象。 像这样:
具体来说,这是一个古老的令牌,用于在公用电话亭拨打电话。 尽管是Bearer令牌,但与OAuth2令牌的类比却很差。
蒂姆·布雷(Tim Bray)在此旧帖子中设计了一张更好的图片: 旅馆钥匙是访问令牌我建议您直接阅读文章,但主要思想是,与我首先链接的实物金属硬币相比,则您的软件令牌可以使用一段时间,可以远程禁用并可以携带信息。
参与的演员
这些是我们的演员:
- 资源所有者
- 客户端(又名应用程序)
- 授权服务器
- 受保护的资源
应该相对直观: 应用程序要访问资源所有者拥有的受保护资源 。 为此,它需要一个令牌。 令牌由Authorization Server发出, Authorization Server是所有其他参与者都信任的第三方实体。
通常,当阅读新内容时,我倾向于快速跳过系统参与者。 也许我不应该,但是在大多数情况下,讨论的段落描述了一个“用户”,但最终使用很多单词来告诉我这只是一个很好的用户……所以我尝试寻找不太直观的术语,并检查其中一些是否具有我应该特别注意的特征。
在OAuth2特定情况下,我觉得名称最混乱的演员是Client 。
我为什么这么说呢? 因为在正常生活中(和IT中),它可能意味着许多不同的事物:用户,专用软件,非常通用的软件……
我更喜欢将其归类为Application 。
强调客户端是我们要向其委派权限的应用程序。 因此,例如,如果该应用程序是我们通过浏览器访问的服务器端Web应用程序, 则客户端不是用户,也不是浏览器本身:客户端是在自己的环境中运行的Web应用程序。
我认为这很重要。 客户术语无处不在,所以我的建议不是完全替换它,而是要让您的大脑牢记客户=应用程序的关系。
我还想认为还有另一个非官方的Actor:用户代理。
我希望我不会在这里使人们感到困惑,因为这完全是我用来构建思维导图的东西。
尽管没有在规范中进行定义,也没有出现在所有不同的流中,但它可以帮助您识别OAuth2流中的第五个Actor。
用户代理在大多数情况下都是由Web浏览器模拟的。 它的职责是在两个彼此不直接交谈的系统之间实现信息的间接传播。 这个想法是:A应该与B对话,但不允许这样做。 因此,A告诉C(用户代理)告诉B某些事情。
目前可能还有些混乱,但是我希望以后能澄清一下。
OAuth2核心业务2
OAuth2关于如何获取令牌。
即使您不是OAuth2的专家,只要有人提到该主题,您可能会立即想到来自Google或其他主要服务提供商的页面,这些页面在您尝试登录您所不依赖的新服务时会弹出还没有帐户,然后告诉Google,是的,您信任该服务,并且希望将对Google的部分权限委派给该服务。
这是正确的,但这只是OAuth2定义的多种可能的交互之一 。
有四个主要方面,您知道这一点很重要。 如果这是您第一次听到,可能会感到惊讶:
并非所有人都会最终向您显示类似Google的权限屏幕!
这是因为您甚至可能希望通过命令行工具来利用OAuth2方法。 甚至根本没有任何用户界面,能够为您显示一个交互式网页以委派权限。
再次记住:主要目标是获得代币!
如果您找到一种获取方式的方法,并且能够使用它们,那么您就完成了。
正如我们所说的,OAuth2框架定义了4种方式。 有时将它们称为流程,有时将其称为Grants 。
你怎么称呼它们并不重要。 我个人使用流程,因为它可以帮助我提醒您,在与不同参与者进行交互以获取令牌时,它们彼此不同。
他们是:
- 授权码流程
- 隐式拨款流
- 客户凭证授予流程
- 资源所有者凭证授予流(又名密码流)
其中每一个都是针对特定方案的建议流程。
为了给您提供一个直观的示例,在某些情况下,您的客户端可以保守秘密(服务器端Web应用程序),而在其他情况下技术上则无法(对于客户端网络应用程序,您可以使用浏览器完全检查其代码) 。
像刚刚描述的那样,环境约束将使整个流程中定义的某些步骤变得不安全(且无用)。 因此,为了简化起见,在完全跳过了一些不可能的交互或未添加任何安全性相关值的交互时,定义了其他流程。
OAuth2 Poster Boy:授权代码流
我们将从以下三个原因开始讨论授权代码流:
- 这是最著名的流程,您可能已经与之交互过(这是类似Google的委托屏幕)
- 这是最复杂,明确和固有的安全性
- 与之相比,其他流程更容易推论
如果客户是受信任的并且能够保密,则应使用“授权代码流”。 这意味着服务器端Web应用程序。
如何使用授权码流程获取令牌
- 所有参与的参与者都信任授权服务器
- 用户(资源所有者)告诉客户(应用程序)代表他做某事
- 客户端将用户重定向到授权服务器,并添加一些参数:
redirect_uri
,response_type=code
,scope
,client_id
- 授权服务器询问用户是否希望代表客户(授权)授予客户端访问特定资源(范围)的某些资源。
- 用户接受委托请求,因此Auth Server现在向User-Agent(浏览器)发送一条指令,以重定向到客户端的url。 还将
code=xxxxx
注入此HTTP重定向指令中。 - 通过HTTP重定向已由User-Agent激活的客户端现在直接与授权服务器对话(绕过User-Agent)。
client_id
,client_secret
和code
(已转发)。 - 授权服务器返回客户端(不是浏览器)有效的
access_token
和refresh_token
它是如此清晰,以至于也被称为OAuth2舞蹈!
让我们强调以下几点:
- 在第2步中,我们在其他参数中指定了
redirect_uri
。 当我们将User-Agent作为参与者之一引入时,这用于实现我们预期的间接通信。 如果我们要允许授权服务器在不打开两者之间的直接网络连接的情况下将信息转发给客户端,则这是关键信息。 - 第2步中提到的
scope
是客户端要求的一组权限 - 请记住,这是在客户端完全受保护时使用的流程。 当客户端与授权服务器之间的通信避免通过安全性较低的User-Agent(可能会嗅探或篡改通信)时,这与步骤5的流程有关。 这也是为什么有意义的是,客户端可以启用更高的安全性,即发送仅在客户端和授权服务器之间共享的
client_secret
。 -
refresh_token
用于客户端可能需要对授权服务器执行的后续自动调用。 当前的access_token
到期并且需要获取新的access_token
时,发送有效的refresh_token
可以避免再次要求用户确认委派。
OAuth2获得了令牌,现在呢?
OAuth2是一个记住框架。 该框架告诉我现在要做什么?
好吧,什么都没有。 = P
这取决于客户开发人员。
她可以(通常应该):
- 检查令牌是否仍然有效
- 查找有关谁授权此令牌的详细信息
- 查找与该令牌相关的权限是什么
- 最终允许访问资源的任何其他有意义的操作
它们都是有效的,而且很明显,对吗? 开发人员是否必须自己找出最佳的操作集才能下一步执行? 她绝对可以。 否则,她可以利用另一个规范:OpenIDConnect(OIDC)。 稍后再详细介绍。
OAuth2 –隐式授权流程
这是为客户端应用程序设计的流程,不能保密 。 客户端HTML应用程序就是一个明显的例子。 但是,即使是任何公开代码的二进制应用程序,也可以被操纵来提取其秘密。
我们不能重新使用授权码流程吗? 是的,但是...如果秘密不再是安全的秘密,步骤5)的意义何在? 从这个额外的步骤中我们将得不到任何保护!
因此,隐式授予流与授权码流相似,但是它不会执行无用的步骤5。 它旨在直接获得access_tokens,而无需先获取密码的中间步骤,该代码将与机密一起交换以获得access_token。
它使用response_type=token
来具体说明在与授权服务器联系时要使用的流。 而且也没有refresh_token
。 这是因为假设用户会话很短(由于安全性较差的环境),并且无论如何,用户仍将重新确认他的委托意愿(这是导致定义的主要用例)的refresh_tokens
)。
OAuth2 –客户凭证授予流程
如果我们没有资源所有者,或者他与客户端软件本身不明确(1:1关系)怎么办?
想象一个仅想与另一个后端系统对话的后端系统。 没有用户参与。 这种交互的主要特征是,它不再是交互性的,因为我们不再需要任何用户来确认他要委派某些东西的意愿。 它还隐式定义了一个更安全的环境,您不必担心主动用户冒着读取机密的风险。
其类型为response_type=client_credentials
。
我们不会在这里详述它,只是要知道它的存在,并且就像前面的流程一样,它是完整OAuth舞蹈的一种变体,实际上是一种简化,如果您的情况允许,建议您使用它。
OAuth2 –资源所有者凭证授予流(又名密码流)
请在这里引起您的注意,因为您将感到困惑。
这是这种情况:资源所有者在授权服务器上拥有一个帐户。 资源所有者将其帐户详细信息提供给客户。 客户端使用此详细信息向授权服务器进行身份验证…
= O
如果您已经进行了讨论,那么您可能会问我是否在开玩笑。 这正是我们在OAuth2探索之初试图摆脱的反模式!
如何找到此处列出的建议流程?
答案实际上是相当合理的: 这是从旧版系统迁移的第一步 。 它实际上比共享密码反模式好一点:
密码是共享的,但这只是启动用于获取令牌的OAuth Dance的一种手段。
如果我们没有更好的选择,这可以让OAuth2站起来。 它引入了access_tokens
的概念,直到体系结构足够成熟(或环境将发生变化)以允许更好和更安全的Flow获取令牌之前,都可以使用它。 另外,请注意,现在令牌是到达受保护资源系统的临时密码,而在完全共享的密码反模式中,这是我们需要转发的密码。
因此,这远非理想,但至少我们通过一些标准证明了这一点。
如何选择最佳流量?
互联网上有很多决策流程图。 我最喜欢的一个是这个:
来自https://auth0.com
它应该可以帮助您记住我在这里给您的简短描述,并根据您的环境选择最简单的流程。
OAuth2返回令牌– JWT
因此,我们现在可以获取令牌。 我们有多种获取途径。 我们没有明确告诉我们如何处理它们,但是通过一些额外的工作和对授权服务器的大量调用,我们可以安排一些事情并获得有用的信息。
情况会更好吗?
例如,我们假设票价如此之高,以致我们的代币可能看起来像这样:
{"access_token": "363tghjkiu6trfghjuytkyen","token_type": "Bearer"
}
我们可以在其中包含更多信息,以便为我们节省前往授权服务器的往返时间吗?
如下所示会更好:
{"active": true,"scope": "scope1 scope2 scope3","client_id": "my-client-1","username": "paolo","iss": "http://keycloak:8080/","exp": 1440538996,"roles" : ["admin", "people_manager"],"favourite_color": "maroon",... : ...
}
我们将能够直接访问一些与资源所有者委托相关的信息。
幸运的是,其他人也有相同的想法,他们提出了JWT – JSON Web令牌 。
JWT是定义代表一组声明的基于JSON的令牌的结构的标准。 正是我们想要的!
实际上,JWT规范提供给我们的最重要方面不是我们上面示例的有效负载,而是在不涉及Authorizatin Server的情况下信任整个令牌的能力!
这怎么可能呢? 这个想法不是一个新想法: JOSE specs在JWT的上下文中定义的非对称签名(pubkey)。
让我为您刷新一下:
在非对称签名中,使用两个密钥来验证信息的有效性。
这两个密钥是耦合的,但是一个是秘密的,只有文档创建者才知道,而另一个是公开的。
秘密用于计算文档的指纹; 哈希。 当文档发送到目的地时,读取器使用与秘密密钥关联的公共密钥来验证文档和他收到的指纹是否有效。 数字签名算法告诉我们,只有通过相应的秘密密钥对文档进行签名后,该文档才是有效的。
总体思路是:如果我们的本地验证通过了,我们可以确保消息已由密钥所有者发布,因此它是隐式可信的。
回到我们的令牌用例:
我们收到令牌。 我们可以信任此令牌吗? 我们在本地验证令牌,而无需联系发行人。 当且仅当基于可信公钥的验证通过时,我们确认令牌有效。 没问题。 如果令牌根据数字标牌是有效的,并且根据声明的寿命有效,那么我们可以将这些信息视为真实信息,而无需向授权服务器请求确认!
可以想象,由于我们将所有信任都放在令牌中,因此不发出寿命过长的令牌可能是明智的:
某人可能已在授权服务器上更改了他的委派首选项,并且该信息可能尚未到达客户端,该客户端仍然具有可以基于其决定进行有效签名的令牌。
最好使事情保持更多同步,发出寿命较短的令牌,因此,最终过时的偏好不会长期受到信任。
OpenID连接
我希望本节不会令您失望,但是本文已经篇幅冗长且内容丰富,因此,我特意使它简短。
OAuth2 + JWT + JOSE〜= OpenID Connect
再一次:OAuth2是一个框架。
OAuth2框架与JWT规范,JOSE和我们将在此处不详述的其他想法(创建OpenID Connect规范)结合使用。
您应该带回的想法是,您可能更经常对使用和利用OpenID Connect感兴趣,因为它汇集了此处定义的最佳方法和想法。
是的,您是在利用OAuth2,但是现在您是OpenID Connect的定义更为明确的界限,它为您提供了更丰富的令牌和对Authentication的支持,而普通的OAuth2从未涵盖过。
一些在线服务可让您在OAuth2或OpenID Connect之间进行选择。 这是为什么?
好吧,当他们提到OpenID Connect时,您知道您正在使用标准。 即使您切换实现,也会有相同的行为。
给出的OAuth2选项可能非常相似,可能具有您可能感兴趣的一些杀手级功能,但在更通用的OAuth2框架之上进行了自定义。 因此,请谨慎选择。
结论
如果您对此主题感兴趣,或者如果本文仅让您更困惑,建议您查看Justin Richer和Antonio Sanso的OAuth 2 in Action 。
另一方面,如果您想检查您的新鲜知识并将其应用于开放源代码授权服务器,我绝对会建议您使用Keycloak ,它具有我们在此描述的所有功能,并且还有更多!
翻译自: https://www.javacodegeeks.com/2017/06/oauth2-jwt-open-id-connect-confusing-things.html