OAuth2.0 OpenID Connect 一
一开始,有一些专有方法可以与外部身份提供者合作进行身份验证和授权。然后是 SAML(安全断言标记语言)——一种使用 XML 作为其消息交换类型的开放标准。然后,出现了 OAuth 和 OAuth 2.0——同样是开放的,也是一种使用 JSON 作为媒介的现代 RESTful 授权方法。现在,“安全委托访问”的圣杯 OpenID Connect(以下简称 OIDC)运行在 OAuth 2.0 之上。
可是等等。OAuth 2.0 有什么问题?为了更好地理解,让我们首先摒弃术语“安全委托访问”。它过于模糊,导致混淆了身份验证 (authn) 和授权 (authz)。
如果没有安全的外部身份验证和授权,您必须相信每个应用程序和每个开发人员不仅会考虑您的最大利益和隐私,而且知道如何保护您的身份并愿意跟上安全最佳实践. 这是一个相当高的要求,对吧?借助 OIDC,您可以使用受信任的外部提供商向给定应用程序证明您就是您所说的那个人,而无需授予该应用程序访问您的凭据的权限。
OAuth 2.0 将很多细节留给了实施者。例如,它支持范围,但未指定范围名称。它支持访问令牌,但未指定这些令牌的格式。使用 OIDC,定义了许多特定的范围名称,每个名称都会产生不同的结果。OIDC 同时具有访问令牌和 ID 令牌。ID 令牌必须是 JSON Web 令牌 (JWT)。由于规范规定了令牌格式,因此可以更轻松地跨实现使用令牌。
关键概念:范围、声明和响应类型
在我们深入了解 OIDC 的细节之前,让我们退后一步,谈谈我们如何与之交互。
所有 OIDC 交互都涉及两个主要参与者:OpenID 提供者 (OP) 和依赖方 (RP)。OP 是一个OAuth 2.0服务器,能够对最终用户进行身份验证,并向依赖方提供有关身份验证结果和最终用户的信息。依赖方是一个 OAuth 2.0 应用程序,它“依赖”OP 来处理身份验证请求。
通常,您通过使用 HTTP GET 访问端点来启动 OIDC 交互/authorization
。许多查询参数指示您在验证后期望返回的内容以及您将有权访问的内容(授权)。
通常,您需要使用/token
HTTP POST 访问端点以获取用于进一步交互的令牌。
OIDC 还有一个/introspect
用于验证令牌的端点,一个/userinfo
用于获取用户身份信息的端点。
以上所有端点都是惯例,但可以由 OP 定义为任何内容。OIDC 的一项重大改进是元数据机制,用于从提供者处发现端点。
什么是范围?
范围是以空格分隔的标识符列表,用于指定请求的访问权限。有效范围标识符在RFC 6749中指定。
OIDC 有许多内置范围标识符。openid
是必需的范围。所有其他 - 包括自定义范围 - 都是可选的。
scope | purpose |
---|---|
profile | requests access to default profile claims |
requests access to email and email_verified claims | |
address | requests access to address claim |
phone | requests access to phone_number and phone_number_verified claims |
默认配置文件声明是:
name
family_name
given_name
middle_name
nickname
preferred_username
profile
picture
website
gender
birthdate
zoneinfo
locale
updated_at
什么是 Claim
简而言之,声明是key/value对,其中包含有关用户的信息以及有关 OIDC 服务的元信息。规范中的官方定义是“关于实体的断言信息”。
这是一组典型的声明:
代码语言:javascript复制{
"family_name": "Silverman",
"given_name": "Micah",
"locale": "en-US",
"name": "Micah Silverman",
"preferred_username": "micah.silverman@okta.com",
"sub": "00u9vme99nxudvxZA0h7",
"updated_at": 1490198843,
"zoneinfo": "America/Los_Angeles"
}
profile
上面包含了一些权利要求。这是因为对用户信息的请求是使用通过范围获得的令牌进行的profile
。换句话说,发出导致令牌发行的请求。该令牌包含基于原始请求中指定范围的某些信息。
什么是响应类型?
使用 OIDC 时,您会听到各种“流”的说法。这些流程用于描述不同的常见身份验证和授权场景。考虑因素包括应用程序的类型(如基于 Web 或本机移动应用程序)、您希望如何验证令牌(在应用程序中或在后端)以及您希望如何访问其他身份信息(进行另一个 API 调用或拥有它直接编码成令牌)。
共有三个主要流程:授权代码、隐式和混合。response_type
这些流由请求中的查询参数控制/authorization
。在考虑使用哪种流程时,请考虑前台渠道与后台渠道的要求。前端通道是指直接与 OpenID 提供商 (OP) 交互的用户代理(例如 SPA 或移动应用程序)。当需要前端通道通信时,隐式流是一个不错的选择。反向通道是指与 OP 交互的中间层客户端(例如 Spring Boot 或 Express)。当需要反向通道通信时,授权代码流是一个不错的选择。
授权代码流使用response_type=code
. 身份验证成功后,响应将包含一个code
值。此代码稍后可以交换 anaccess_token
和 an id_token
(暂时挂起,稍后我们将更深入地讨论令牌。)当您将“中间件”作为体系结构的一部分时,此流程很有用。中间件有一个client id
and client secret
,这是通过点击端点来交换code
for 令牌所必需的/token
。然后可以将这些令牌返回给最终用户应用程序,例如浏览器,而浏览器不必知道client secret
. 此流程允许通过使用refresh tokens
. 的唯一目的refresh tokens
是获取新的access tokens
以扩展用户会话。
隐式流使用response_type=id_token token
or response_type=id_token
。身份验证成功后,响应将在第一种情况下包含一个id_token
和一个,在第二种情况下仅包含一个。当您有一个应用程序直接与后端对话以获取没有中间件的令牌时,此流程很有用。它不支持长期会话。access_token``id_token
混合流以不同的组合结合了上述两者——任何对用例有意义的东西。一个例子是response_type=code id_token
。这种方法实现了一种场景,您可以在应用程序中进行长期会话并立即从端点取回令牌/authorization
。
关于令牌
有了范围、声明和响应类型的基础,我们现在可以谈论令牌了!OIDC 中存在三种类型的令牌:id_token
、access_token
和refresh_token
。
id-tokens
根据OIDC 规范, Anid_token
是JWT。这意味着:
- 有关用户的身份信息被编码到令牌中,并且
- 令牌可以被最终验证以证明它没有被篡改。
规范中有一组规则id_token
用于验证. 在 中编码的声明中有id_token
一个过期 ( exp
),必须将其视为验证过程的一部分。此外,JWT 的签名部分与密钥一起使用,以验证整个 JWT 未以任何方式被篡改。
JWT
一开始,JWT是不透明的——它们不携带任何内在信息。这很好,因为服务器知道令牌并可以查找与其相关的任何数据,例如身份信息。
2012 年发布OAuth 2.0 规范时,它定义了令牌类型(例如访问和刷新令牌),但它有意避免规定这些令牌的格式。
2015 年,JWT 规范发布。它提议创建对其他信息进行编码的令牌。该令牌可以用作不透明标识符,也可以检查其他信息——例如身份属性。它调用这些属性claims
。该规范还包括对加密签名的 JWT(称为 JWS)和加密的 JWT(称为 JWE)的规定。签名的 JWT 在应用程序开发中特别有用,因为您可以高度确信编码到 JWT 中的信息未被篡改。通过在应用程序中验证 JWT,您可以避免到 API 服务的另一次往返。它还允许强制执行行为,例如过期,因为您知道声明exp
没有被更改。
JWT 和 OAuth 2.0 之间没有直接关系。然而,许多 OAuth 2.0 实施者看到了 JWT 的好处,并开始将它们用作(或两者)访问和刷新令牌。
OIDC 正式规定了 JWT 在强制 ID 令牌成为 JWT 方面的作用。许多 OIDC 实施者也会将 JWT 用于访问和刷新令牌,但这不是由规范规定的。
Access Token
访问令牌用作不记名令牌。持有者令牌意味着持有者无需进一步识别即可访问授权资源。因此,保护不记名令牌非常重要。如果我能以某种方式获得并“携带”你的访问令牌,我就可以伪装成你。
这些令牌通常具有较短的生命周期(由其到期决定)以提高安全性。也就是说,当访问令牌过期时,用户必须再次进行身份验证才能获得新的访问令牌,从而限制它是不记名令牌这一事实的暴露。
尽管 OIDC 规范并未强制要求,但 Okta 将 JWT 用于访问令牌,因为(除其他事项外)过期是内置在令牌中的。
OIDC 指定/userinfo
返回身份信息且必须受到保护的端点。出示访问令牌使端点可访问。
下面是一个使用HTTPie的例子:
代码语言:javascript复制http https://micah.oktapreview.com/oauth2/.../v1/userinfo
HTTP/1.1 400 Bad Request
...
WWW-Authenticate: Bearer error="invalid_request", error_description="The access token is missing."
...
让我们使用过期的访问令牌再试一次:
代码语言:javascript复制http https://micah.oktapreview.com/oauth2/.../v1/userinfo
Authorization:"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNelJhOEV1elY5emgyREl6X3RVRUkifQ..."
HTTP/1.1 401 Unauthorized
...
WWW-Authenticate: Bearer error="invalid_token", error_description="The token has expired."
...
最后,让我们尝试使用有效的访问令牌:
代码语言:javascript复制http https://micah.oktapreview.com/oauth2/.../v1/userinfo
Authorization:"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNelJhOEV1elY5emgyREl6X3RVRUkifQ..."
HTTP/1.1 200 OK
...
{
"family_name": "Silverman",
"given_name": "Micah",
"groups": [
"ABC123",
"Everyone"
],
"locale": "en-US",
"name": "Micah Silverman",
"preferred_username": "micah okta@afitnerd.com",
"sub": "...",
"updated_at": 1490198843,
"zoneinfo": "America/Los_Angeles"
}
refresh-tokens
刷新令牌用于获取新的访问令牌。通常,刷新令牌将长期存在,而访问令牌将是短暂的。这允许在必要时可以终止的长期会话。这是一个典型的场景:
- 用户登录并取回访问令牌和刷新令牌
- 应用程序检测到访问令牌已过期
- 应用程序使用刷新令牌获取新的访问令牌
- 重复 2 和 3,直到刷新令牌过期
- 刷新令牌过期后,用户必须重新进行身份验证
你可能会问:为什么要这么做?这种方法在用户体验和安全性之间取得了平衡。想象一下,如果用户以某种方式受到损害。或者,他们的订阅到期。或者,他们被解雇了。在任何时候,管理员都可以撤销刷新令牌。然后,上面的第三步将失败,用户将被迫(尝试)通过身份验证建立一个新会话。如果他们的帐户已被暂停,他们将无法进行身份验证。
识别令牌类型
有时区分不同的令牌类型可能会造成混淆。这是一个快速参考:
- ID token 携带在 token 本身编码的身份信息,必须是 JWT
- 访问令牌用于通过将资源用作不记名令牌来获取对资源的访问权限
- 刷新令牌的存在仅仅是为了获得更多的访问令牌