OAuth2、JWT、Open-ID Connect 等令人困惑的东西

位置:首页>文章>详情   分类:Java教程   阅读(126)   2023-12-06 16:34:57

免责声明

如果我觉得我必须以一个重要的免责声明开始这篇文章:不要太相信我要说的话。 之所以这么说,是因为我们在讨论安全问题。 当您谈论安全性时,除了 100% 正确的陈述之外,其他任何事情都有可能使您面临任何类型的风险。 因此,请在阅读这篇文章时牢记,您的真实来源应该是官方规范,这只是我用来在脑海中回顾这个主题的概述并将其介绍给初学者。

使命

我决定写这篇文章是因为我一直觉得 OAuth2 令人困惑。即使现在我对它有了更多的了解,我还是发现它的某些部分令人费解。即使当我需要摆弄他们的 API 时我能够遵循来自 Google 或 Pinterest 之类的在线教程,它总是感觉像某种巫术,所有这些代码和 Bearer 令牌。每次他们提到我可以为特定步骤做出自己的决定,在标准的 OAuth2 方法中进行选择时,我的头脑往往会变得盲目。

我希望我能修正一些想法,这样从现在开始,你就能更有信心地学习 OAuth2 教程。

什么是 OAuth2?

让我们从定义开始:

OAuth 2 是一个授权框架,它使应用程序能够在HTTP 上获得对用户帐户的有限访问权限服务

上面这句话是合理理解的,但是如果我们准确地指出所选择的术语,我们可以改进事情。

名称的 Auth 部分显示为 Authorisation(它可能是 Authentication;但不是)。 Framework 很容易被忽视,因为术语 framework 经常被滥用;但保留在这里的想法是它不一定是最终产品或完全定义的东西。这是一个工具集。一系列想法、方法和明确定义的交互,您可以使用它们在其之上构建某些东西!它使应用程序能够获得有限的访问权限。这里的关键是它启用非人类的应用程序对用户帐户的有限访问 可能是定义的关键部分,可以帮助您记住和解释 OAuth2 是什么:主要目的是允许用户委托访问用户拥有的资源。将其委托给应用程序。

OAuth2 是关于委托的。

这是关于一个人,指示一个软件代表她做某事。该定义还提到了有限的访问权限,因此您可以想象能够委派您的部分能力。最后提到 HTTP 服务。此授权委托发生在 HTTP 服务上

OAuth2 之前的委托

现在上下文应该更清楚了,我们可以问问自己:在 OAuth2 和类似概念出现之前,事情是如何进行的?

好吧,大多数时候,情况正如您所猜测的那样糟糕:有一个共享的秘密

如果我想让软件 A 被授予访问我在服务器 B 上的东西的权限,大多数情况下,方法是将我的用户/通行证提供给软件 A,以便它可以代表我使用它。这仍然是您可以在许多现代软件中看到的一种模式,我个人希望它是让您感到不舒服的东西。您知道他们怎么说:如果您分享秘密,它就不再是秘密!

现在想象一下,如果您可以为每个需要共享内容的服务创建一个新的管理员/密码对。我们称它们为临时密码。它们与特定服务的主帐户有所不同,但它们仍然允许访问与您相同的服务。 在这种情况下,您可以进行委托,但您仍需负责跟踪您需要创建的所有这些新的仅限应用程序的帐户。

OAuth2——想法

请记住,我们试图解决的业务问题是“委托”问题,我们希望扩展临时密码的想法,以减轻用户管理这些临时密码的负担。 OAuth2 将这些ad-hoc 密码称为令牌令牌,实际上不止于此,我将尝试对其进行说明,但将它们与这个更简单的临时密码概念联系起来可能会很有用首先。

OAuth2——核心业务

Oauth 2 核心业务是关于:

  • 如何获得代币

OAuth2 – 什么是令牌?

既然一切似乎都围绕着代币,那什么是代币呢?我们已经使用了 ad-hoc 密码的类比,到目前为止它对我们很有帮助,但也许我们可以做得更好。如果我们在 OAuth2 规范中寻找答案怎么办?好吧,准备好失望吧。 OAuth2 规范没有提供如何定义令牌的详细信息。为什么这甚至是可能的?还记得我们说过 OAuth2 “只是一个框架”吗?好吧,这是定义很重要的情况之一!规范只是告诉你代币是什么的逻辑定义,并描述它需要具备的一些能力。但最后,规范说 a token 是一个字符串。包含用于访问资源的凭据的字符串。 它提供了更多详细信息,但可以说大多数时候,令牌中的内容并不重要。只要应用程序能够使用它们。

令牌就是这样一种东西,它允许应用程序访问您感兴趣的资源。

为了指出如何避免过度思考代币是什么,规范还明确指出“通常对客户来说是不透明的”!他们实际上是在告诉你,你甚至不需要理解他们!要记住的事情更少,听起来不错!

但是为了避免把它变成纯粹的哲学课,让我们展示一下代币可以是什么

{
   "access_token": "363tghjkiu6trfghjuytkyen",
   "token_type": "Bearer"
}

快速一瞥告诉我们,是的,这是一个字符串。 json-like,不过这可能只是因为最近流行json,不一定是必须的。我们可以发现一个看起来像随机字符串的部分,一个 id:363tghjkiu6trfghjuytkyen。程序员知道,当你看到这样的东西时,至少当字符串不太长时,这可能表明它只是一个可以与存储在其他地方的更详细信息相关联的键。在这种情况下也是如此。更具体地说,附加信息将是有关该代码所代表的特定授权的详细信息。

但是还有一件事应该引起您的注意:"token_type": "Bearer"

您的合理问题应该是:Bearer 令牌类型的特征是什么?还有其他类型吗?哪个?

幸运的是,我们努力让事情变得简单,答案很简单(有些人可能会说,太容易混淆了……)

规范只讨论 Bearer 令牌类型!

呃,为什么设计代币的人觉得他必须指定唯一已知的值?您可能会在这里开始看到一种模式:因为 OAuth2 只是一个框架!它会建议您如何做事,并为您做出一些选择做一些繁重的工作,但最后,您有责任使用该框架来构建您想要的东西。我们只是话虽如此,尽管这里我们只讨论 Bearer 标记,但这并不意味着您不能定义您的自定义类型,您可以赋予它某种意义。

好吧,只有一个类型。 但这是一个奇怪的名字。该名称是否暗示任何相关内容?也许这是一个愚蠢的问题,但对于像我这样的非英语母语者来说,Bearer 在这种情况下的含义可能会有些混乱。

它的含义其实很简单:

不记名令牌是指如果您拥有有效令牌,我们就会信任您。没有问题。

如此简单,令人困惑。你可能会争辩说:“好吧,现实世界中所有类似代币的对象都是这样工作的:如果我有有效货币,你就可以用它们换取你出售的商品”。

正确的。这是 Bearer Token 的一个有效示例。

但并不是每个令牌都是种类 Bearer。 机票,例如,它不是不记名令牌。 仅凭机票获准登机是不够的。您还需要出示有效身份证件,以便与您的机票进行匹配;如果你的名字和车票相符,你的脸和身份证相符,你就可以上车了。

总结一下,我们正在使用一种令牌,如果您拥有其中一个令牌,就足以访问资源。

让您思考:我们说过 OAuth2 是关于授权的。如果您想将具有此特征的令牌传递给某人进行委托,则显然很方便。

令牌类比

再一次,这可能是我的非英语母语背景建议我澄清一下。当我在意大利语(我的母语)中查找 token 的第一个翻译时,我被指向了一个物理对象。是这样的:

那,具体来说,是一种古老的令牌,用来在公用电话亭打电话。尽管是 Bearer 令牌,但它与 OAuth2 令牌的类比很差。 Tim Bray 在这篇旧帖子中设计了一个更好的图片:An Hotel Key is an Access Token我建议你直接阅读这篇文章,但主要思想是,与实物相比我首先链接的金属硬币,你的软件令牌可以有一个生命周期,可以远程禁用并且可以携带信息。

涉及的演员

这些是我们的演员:

  • 资源所有者
  • 客户端(又名应用程序)
  • 授权服务器
  • 受保护的资源

它应该相对直观:应用程序想要访问资源所有者拥有的受保护资源。为此,它需要一个令牌。令牌由授权服务器发出,这是所有其他参与者都信任的第三方实体。

通常,当阅读新内容时,我倾向于快速跳过系统的参与者。可能我不应该,但大多数时候,谈话的段落描述,例如,一个“用户”,最终使用许多词来告诉我它只是,好吧,一个用户......所以我试图寻找不太直观的术语,并检查其中是否有一些我应该特别注意的特征。

在 OAuth2 特定案例中,我觉得名字最容易混淆的角色是 Client。我为什么这么说?因为,在正常生活(以及 IT 领域)中,它可能意味着许多不同的事物:用户、专用软件、非常通用的软件……

我更愿意将其归类为Application

强调客户端是我们要将权限委托给的应用程序。因此,例如,如果应用程序是我们通过浏览器访问的服务器端 Web 应用程序,客户端不是用户或浏览器本身:客户端是在其自己的环境中运行的 Web 应用程序。

我认为这是非常重要的。客户端术语随处可见,所以我的建议不是完全替换它,而是强迫您的大脑牢记 Client = Application 的关系。

我还认为还有另一个非官方的 Actor:User-Agent。

我希望我不会混淆这里的人,因为这完全是我用来构建我的思维导图的东西。尽管没有在规范中定义,也没有出现在所有不同的流程中,但它可以帮助识别 OAuth2 流程中的第五个参与者。 User-Agent 大部分时间由 Web 浏览器模拟。它的职责是在两个彼此不直接对话的系统之间实现信息的间接传播。这个想法是:A 应该与 B 交谈,但不允许这样做。所以 A 告诉 C(用户代理)告诉 B 一些事情。

目前可能还是有点混乱,但我希望稍后能够澄清这一点。

OAuth2核心业务2

OAuth2 是关于如何获取令牌的。

即使您不是 OAuth2 专家,一旦有人提到该主题,您可能会立即想到来自 Google 或其他主要服务提供商的那些页面,这些页面在您尝试登录您不熟悉的新服务时弹出还没有帐户,然后告诉 Google,是的,您信任该服务,并且您想将您在 Google 上拥有的部分权限委托给该服务。

这是正确的,但是这只是 OAuth2 定义的多种可能交互之一

有 4 个主要的你知道很重要。如果这是您第一次听到它,您可能会感到惊讶:并非所有这些最终都会向您显示类似 Google 的权限屏幕!那是因为您可能希望利用 OAuth2 方法,即使从命令行工具;甚至可能根本没有任何 UI,能够向您显示一个交互式网页来委派权限。

再次记住:主要目标是获得代币!

如果你找到一种方法来获得一个,即“如何”部分,并且你能够使用它们,那么你就完成了。

正如我们所说,OAuth2 框架定义了 4 种方式。 有时它们被称为流量,有时它们被称为赠款。你怎么称呼他们并不重要。 我个人使用流程,因为它有助于提醒我,它们彼此不同,因为您必须与不同的参与者进行交互才能获得代币。

他们是:

  • 授权代码流程
  • 隐式授权流程
  • 客户端凭据授予流程
  • 资源所有者凭据授予流程(又名密码流程)

其中每一个都是针对特定场景的建议流程。举一个直观的例子,在某些情况下,您的客户端能够保密(服务器端 Web 应用程序),而在其他情况下它在技术上不能(客户端 Web 应用程序,您可以使用浏览器完全检查它的代码) .像刚刚描述的那样的环境约束会使完整流程中定义的某些步骤变得不安全(和无用)。因此,为了使其更简单,当一些不可能的交互或没有增加任何安全相关价值的交互被完全跳过时,定义了其他流程。

OAuth2 海报男孩:授权代码流

我们将从授权代码流开始讨论,原因有以下三个:

  • 这是最著名的流程,您可能已经与之交互过(这是类似 Google 的委托屏幕流程)
  • 它是最复杂、最明确且本质上安全的
  • 与此相比,其他流程更容易推理

如果您的客户值得信赖并且能够保守秘密,则授权代码流程是您应该使用的流程。这意味着服务器端 Web 应用程序。

如何使用授权代码流获取令牌

  1. 所有相关的Actor都信任授权服务器
  2. 用户(资源所有者)告诉客户(应用程序)代表他做某事
  3. 客户端将用户重定向到授权服务器,添加一些参数:redirect_uriresponse_type=codescopeclient_id
  4. 授权服务器询问用户是否希望以特定权限(范围)代表他(委派)授予客户端访问某些资源的权限。
  5. 用户接受委托请求,因此 Auth Server 现在向 User-Agent(浏览器)发送一条指令,以重定向到客户端的 url。它还将 code=xxxxx 注入到此 HTTP 重定向指令中。
  6. 由于 HTTP 重定向,已由用户代理激活的客户端现在直接与授权服务器对话(绕过用户代理)。 client_idclient_secretcode(已转发)。
  7. 授权服务器向客户端(不是浏览器)返回一个有效的 access_token 和一个 refresh_token

它是如此清晰,以至于它也被称为 OAuth2 舞蹈!

让我们强调几点:

  • 在第 2 步,我们在其他参数中指定了一个 redirect_uri。这用于实现我们在将 User-Agent 作为参与者之一引入时预期的间接通信。如果我们想让授权服务器在两者之间没有打开直接网络连接的情况下将信息转发给客户端,这是一个关键信息。
  • 第 2 步中提到的 scope 是客户要求的权限集
  • 请记住,这是您在客户端完全安全时使用的流程。它与此流程中的第 5 步相关,当客户端和授权服务器之间的通信避免通过安全性较低的用户代理(可能会嗅探或篡改通信)时。这也是为什么客户端启用更多安全性是有意义的,即发送它的 client_secret,这仅在他和授权服务器之间共享。
  • refresh_token 用于客户端可能需要对授权服务器执行的后续自动调用。当当前的 access_token 过期并且需要获得新的时,发送有效的 refresh_token 可以避免再次询问用户以确认委托。

OAuth2 得到一个令牌,现在怎么办?

OAuth2 是一个框架,请记住。框架告诉我现在做什么?

好吧,没什么。 =P

这取决于客户端开发人员。

她可以(而且经常应该):

  • 检查令牌是否仍然有效
  • 查找有关谁授权此令牌的详细信息
  • 查找与该令牌关联的权限是什么
  • 最终授予对资源的访问权限的任何其他操作

它们都是有效的,而且非常明显,对吧?开发人员是否必须自己找出下一步要执行的最佳操作集?她绝对可以。否则,她可以利用另一个规范:OpenIDConnect(OIDC)。稍后会详细介绍。

OAuth2 – 隐式授权流程

它是为无法保密的客户端应用程序设计的流程。一个明显的例子是客户端 HTML 应用程序。但即使是任何代码向公众公开的二进制应用程序也可以被操纵以提取其秘密。我们不能重新使用授权代码流程吗?是的,但是……如果秘密不再是安全秘密,步骤 5) 的意义何在?我们没有从这个额外的步骤中得到任何保护!所以,Implicit Grant Flow,类似于Authorization Code Flow,但是它没有执行那个无用的步骤5。与secret一起交换,得到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获取token。另外,请注意,现在令牌是到达受保护资源系统的临时密码,而在完全共享的密码反模式中,需要转发的是我们的密码。

所以,远非理想,但至少我们通过一些标准证明了这一点。

如何选择最佳流量?

网上有很多决策流程图。我最喜欢的其中之一是这个:

来自 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 Tokens。 JWT 是一种标准,用于定义表示一组声明的基于 JSON 的令牌结构。正是我们要找的!

实际上,JWT 规范给我们的最重要的方面不是我们上面举例说明的有效负载,而是信任整个令牌而不涉及授权服务器的能力!

这怎么可能呢?这个想法并不新鲜:非对称签名 (pubkey),由 JOSE specs 在 JWT 的上下文中定义。

让我为您刷新一下:

在非对称签名中,使用两个密钥来验证信息的有效性。这两个密钥是耦合的,但一个是秘密的,只有文档创建者知道,而另一个是公开的。 secret one用于计算文档的指纹;哈希。当文件被发送到目的地时,读者使用与秘密密钥相关联的公钥来验证他收到的文件和指纹是否有效。数字签名算法告诉我们,根据公钥,文档是有效的,只有当它被相应的秘密密钥签名时。

总体思路是:如果我们的本地验证通过,我们可以确定该消息已由密钥所有者发布,因此它是隐式信任的。

回到我们的代币用例:

我们收到一个令牌。我们可以信任这个令牌吗?我们在本地验证令牌,无需联系发行人。当且仅当基于可信公钥的验证通过时,我们确认令牌有效。没有问任何问题。如果令牌根据数字标牌是有效的,并且如果根据其声明的生命周期它是有效的,我们可以将这些信息视为真实的,我们不需要向授权服务器请求确认!

正如您可以想象的那样,由于我们将所有这些信任都放在令牌上,因此不发出具有过长生命周期的令牌可能是明智的:某人可能已经在授权服务器上更改了他的委托首选项,并且该信息可能没有到达客户端,它仍然有一个有效的和签名的令牌,它可以根据它的决定。最好让事情更加同步,发出寿命较短的代币,因此,最终过时的偏好不会有长期被信任的风险。

OpenID 连接

我希望这部分不会让您失望,但是这篇文章已经很长而且信息量很大,所以我会故意保持简短。

OAuth2 + JWT + JOSE ~= OpenID Connect

再次声明:OAuth2 是一个框架。 OAuth2 框架结合 JWT 规范、JOSE 和其他我们在这里不详细介绍的想法,创建 OpenID Connect 规范。

您应该带回的想法是,您可能更经常对使用和利用 OpenID Connect 感兴趣,因为它汇集了此处定义的最佳方法和想法。是的,您正在利用 OAuth2,但您现在是 OpenID Connect 更明确的边界,它为您提供更丰富的令牌和对身份验证的支持,这是普通 OAuth2 从未涵盖的。

一些在线服务允许您在 OAuth2 或 OpenID Connect 之间进行选择。这是为什么?好吧,当他们提到 OpenID Connect 时,您就知道您使用的是一个标准。即使您切换实现方式,也会以相同的方式运行。您获得的 OAuth2 选项可能非常相似,可能具有您可能感兴趣的一些杀手级功能,但自定义构建在更通用的 OAuth2 框架之上。所以要谨慎选择。

总结

如果您对此主题感兴趣,或者如果这篇文章只是让您更加困惑,我建议您查看 Justin Richer 和 Antonio Sanso 的OAuth 2 in Action。另一方面,如果您想检查您的新知识并尝试将其应用到开源授权服务器,我绝对会推荐使用 Keycloak ,它能够完成我们所拥有的一切在这里描述等等!

标签: Java教程
地址:https://www.cundage.com/article/jcg-oauth2-jwt-open-id-connect-confusing-things.html