前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OAuth2.0 OpenID Connect 三

OAuth2.0 OpenID Connect 三

作者头像
用户1418987
发布2023-10-16 09:26:06
2680
发布2023-10-16 09:26:06
举报
文章被收录于专栏:coder

OAuth2.0 OpenID Connect 三

JWT 的好处是能够在其中携带信息。有了可用于您的应用程序的此信息,您可以轻松强制执行令牌过期并减少 API 调用次数。此外,由于它们经过加密签名,您可以验证它们是否未被篡改。

根据 OIDC 规范的规定,与身份相关的信息有两个主要来源。id_token 一个来源是编码到JWT中的信息。另一个是来自端点的响应/userinfo,可以使用access_token作为不记名令牌访问。

请求中有很多查询参数的组合/authorization,它们决定了哪些信息将被编码到id_token. 影响最终将在返回的令牌和/userinfo端点中找到的内容的两个查询参数是response_typescope

OIDC 响应类型

目前,我们将搁置scope并专注于response_type. 在以下示例中,我们仅使用范围openid(必需)和email. 我们还将使用隐式流,因为它会立即返回令牌。

鉴于此要求:

代码语言:javascript
复制
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize?client_id=0oa2yrbf35Vcbom491t7&response_type=token&scope=openid+email&state=aboard-insect-fresh-smile&nonce=c96fa468-ca1b-46f0-8974-546f23f9ee6f&redirect_uri=https%3A%2F%2Fokta-oidc-fun.herokuapp.com%2Fflow_result

请注意,这response_type=token会给我们一个access_token. OIDC 规范中不需要访问令牌的特定格式,我们使用 JWT。查看返回的令牌,我们看到:

代码语言:javascript
复制
{
	"active": true,
	"scope": "openid email",
	"username": "okta_oidc_fun@okta.com",
	"exp": 1501531801,
	"iat": 1501528201,
	"sub": "okta_oidc_fun@okta.com",
	"aud": "test",
	"iss": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
	"jti": "AT.upPJqU-Ism6Fwt5Fpl8AhNAdoUeuMsEgJ_VxJ3WJ1hk",
	"token_type": "Bearer",
	"client_id": "0oa2yrbf35Vcbom491t7",
	"uid": "00u2yulup4eWbOttd1t7"
}

这主要是资源信息,包括过期时间 ( exp) 和用户 ID ( uid)。

如果我们想要获取用户的身份信息,我们必须使用作为不记名令牌的/userinfo端点。这是使用HTTPieaccess_token的样子:

代码语言:javascript
复制
http https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/userinfo Authorization:"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNelJhOEV1elY5emgyREl6X3RVRUkifQ..."
HTTP/1.1 200 OK
...
{
	"sub": "00u2yulup4eWbOttd1t7",
	"email": "okta_oidc_fun@okta.com",
	"email_verified": true
}

我们取回sub,emailemail_verified。这是因为scope=openid+email原始请求的默认值。我们将在范围部分查看一些更详细的响应。

让我们尝试另一个请求:

代码语言:javascript
复制
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize?client_id=0oa2yrbf35Vcbom491t7&response_type=id_token&scope=openid+email&state=aboard-insect-fresh-smile&nonce=c96fa468-ca1b-46f0-8974-546f23f9ee6f&redirect_uri=https%3A%2F%2Fokta-oidc-fun.herokuapp.com%2Fflow_result

这一次,我通过使用请求 ID 令牌response_type=id_token。响应是一个 JWT(根据 OIDC 规范的要求),其中编码了以下信息:

代码语言:javascript
复制
{
	"sub": "00u2yulup4eWbOttd1t7",
	"email": "okta_oidc_fun@okta.com",
	"ver": 1,
	"iss": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
	"aud": "0oa2yrbf35Vcbom491t7",
	"iat": 1501528456,
	"exp": 1501532056,
	"jti": "ID.4Mmzy2kj5_B8nGZ_PT4dt8-fzu1tA2W3C5dbEF-N6Us",
	"amr": [
		"pwd"
	],
	"idp": "00o1zyyqo9bpRehCw1t7",
	"nonce": "c96fa468-ca1b-46f0-8974-546f23f9ee6f",
	"email_verified": true,
	"auth_time": 1501528157
}

请注意,我们将subemail声明直接编码在 JWT 中。在这种类型的隐式流程中,我们没有可用于端点的不记名令牌/userinfo,因此身份信息被直接设置到 JWT 中。

最后我们来看最后一种隐式流:

代码语言:javascript
复制
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize?client_id=0oa2yrbf35Vcbom491t7&response_type=id_token+token&scope=openid+email&state=aboard-insect-fresh-smile&nonce=c96fa468-ca1b-46f0-8974-546f23f9ee6f&redirect_uri=https%3A%2F%2Fokta-oidc-fun.herokuapp.com%2Fflow_result

在这里,我们在响应中请求 id_tokenaccess_token`

我们的access_token声明与以前相同。具有id_token以下内容:

代码语言:javascript
复制
{
	"sub": "00u2yulup4eWbOttd1t7",
	"email": "okta_oidc_fun@okta.com",
	"ver": 1,
	"iss": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
	"aud": "0oa2yrbf35Vcbom491t7",
	"iat": 1501528536,
	"exp": 1501532136,
	"jti": "ID.fyybPizTmYLoQR20vlR7mpo8WTxB7JwkxplMQom-Kf8",
	"amr": [
		"pwd"
	],
	"idp": "00o1zyyqo9bpRehCw1t7",
	"nonce": "c96fa468-ca1b-46f0-8974-546f23f9ee6f",
	"auth_time": 1501528157,
	"at_hash": "T7ij7o69gBtjo6bAJvaVBQ"
}

请注意,此时信息较少id_token(在本例中,没有email_verified声明)。因为我们还请求了access_token,所以预计我们将从端点获取其余的可用身份信息(基于范围)/userinfo。在这种情况下,当我们只请求access_token

OIDC 范围

将所有可用范围与所有可能的响应类型相结合,会产生大量要呈现的信息:准确地说是 48 种组合。首先,我将列举每个范围产生的结果,然后我们将看一些结合 和 的真实世界request_type示例scope

id_token首先要注意的是,不同的范围会对端点中编码的信息和从端点返回的信息产生影响/userinfo

scope

resultant claims

openid

(required for all OIDC flows)

profile

name, family_name, given_name, middle_name, nickname, preferred_username

profile (cont’d)

profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at

email

email, email_verified

address

address

phone

phone_number, phone_number_verified

让我们尝试使用所有可能的(默认)范围类型的每个隐式流。

代码语言:javascript
复制
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize?client_id=0oa2yrbf35Vcbom491t7&response_type=token&scope=openid+profile+email+address+phone&state=aboard-insect-fresh-smile&nonce=c96fa468-ca1b-46f0-8974-546f23f9ee6f&redirect_uri=https%3A%2F%2Fokta-oidc-fun.herokuapp.com%2Fflow_result

结果access_token与之前的唯一区别是所有范围都被编码到scp数组声明中。

这一次,当我使用access_token到达端点时/userinfo,我得到了更多信息:

代码语言:javascript
复制
http https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/userinfo Authorization:"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNelJhOEV1elY5emgyREl6X3RVRUkifQ..."
HTTP/1.1 200 OK
...
{
	"sub": "00u2yulup4eWbOttd1t7",
	"name": "Okta OIDC Fun",
	"locale": "en-US",
	"email": "okta_oidc_fun@okta.com",
	"preferred_username": "okta_oidc_fun@okta.com",
	"given_name": "Okta OIDC",
	"family_name": "Fun",
	"zoneinfo": "America/Los_Angeles",
	"updated_at": 1499922371,
	"email_verified": true
}

注意:虽然它不是从profile范围定义的声明的完整列表,但它是我在 Okta 中的用户具有值的所有声明。

让我们只尝试id_token隐式流程(仍然使用所有默认范围):

代码语言:javascript
复制
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize?client_id=0oa2yrbf35Vcbom491t7&response_type=id_token&scope=openid+profile+email+address+phone&state=aboard-insect-fresh-smile&nonce=c96fa468-ca1b-46f0-8974-546f23f9ee6f&redirect_uri=https%3A%2F%2Fokta-oidc-fun.herokuapp.com%2Fflow_result

id_token这是编码到我回来的内容:

代码语言:javascript
复制
{
	"sub": "00u2yulup4eWbOttd1t7",
	"name": "Okta OIDC Fun",
	"locale": "en-US",
	"email": "okta_oidc_fun@okta.com",
	"ver": 1,
	"iss": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
	"aud": "0oa2yrbf35Vcbom491t7",
	"iat": 1501532222,
	"exp": 1501535822,
	"jti": "ID.Zx8EclaZmhSckGHOCRzOci2OaduksmERymi9-ad7ML4",
	"amr": [
		"pwd"
	],
	"idp": "00o1zyyqo9bpRehCw1t7",
	"nonce": "c96fa468-ca1b-46f0-8974-546f23f9ee6f",
	"preferred_username": "okta_oidc_fun@okta.com",
	"given_name": "Okta OIDC",
	"family_name": "Fun",
	"zoneinfo": "America/Los_Angeles",
	"updated_at": 1499922371,
	"email_verified": true,
	"auth_time": 1501528157
}

所有(可用的)身份信息都被编码到令牌中,因为我没有用于访问端点的不记名令牌/userinfo

最后,让我们尝试隐式流的最后一个变体response_type=id_token+token

代码语言:javascript
复制
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize?client_id=0oa2yrbf35Vcbom491t7&response_type=code+id_token+token&scope=openid+profile+email+address+phone&state=aboard-insect-fresh-smile&nonce=c96fa468-ca1b-46f0-8974-546f23f9ee6f&redirect_uri=https%3A%2F%2Fokta-oidc-fun.herokuapp.com%2Fflow_result

在这种情况下,我们将一些声明编码到id_token

代码语言:javascript
复制
{
	"sub": "00u2yulup4eWbOttd1t7",
	"name": "Okta OIDC Fun",
	"email": "okta_oidc_fun@okta.com",
	"ver": 1,
	"iss": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
	"aud": "0oa2yrbf35Vcbom491t7",
	"iat": 1501532304,
	"exp": 1501535904,
	"jti": "ID.1C2NQext2hM0iJy55cLc_Ryc45urVYC1wJ0S-KebkpI",
	"amr": [
		"pwd"
	],
	"idp": "00o1zyyqo9bpRehCw1t7",
	"nonce": "c96fa468-ca1b-46f0-8974-546f23f9ee6f",
	"preferred_username": "okta_oidc_fun@okta.com",
	"auth_time": 1501528157,
	"at_hash": "GB5O9CpSSOUSfVZ9CRekRg",
	"c_hash": "mRNStYQm-QU4rwcfv88VKA"
}

如果我们使用结果access_token来达到/userinfo终点,在这种情况下,我们会得到:

代码语言:javascript
复制
http https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/userinfo Authorization:"Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNelJhOEV1elY5emgyREl6X3RVRUkifQ..."
HTTP/1.1 200 OK
...
{
	"sub": "00u2yulup4eWbOttd1t7",
	"name": "Okta OIDC Fun",
	"locale": "en-US",
	"email": "okta_oidc_fun@okta.com",
	"preferred_username": "okta_oidc_fun@okta.com",
	"given_name": "Okta OIDC",
	"family_name": "Fun",
	"zoneinfo": "America/Los_Angeles",
	"updated_at": 1499922371,
	"email_verified": true
}

这完善了范围中请求的所有身份信息。

自定义范围和声明

OIDC 规范适应自定义范围和声明。在令牌中包含自定义声明的能力(可通过密码验证)是身份提供者的一项重要功能。Okta 的实现为此提供了支持。

下面的屏幕截图显示了我的授权服务器的声明选项卡:

单击“添加声明”按钮会弹出一个对话框:

response_type=id_token使用带有and的隐式流scope=openid+profile,我们现在返回一个id_token其中编码了这些声明的 an :

代码语言:javascript
复制
{
	"sub": "00u2yulup4eWbOttd1t7",
	"ver": 1,
	"iss": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
	"aud": "0oa2yrbf35Vcbom491t7",
	"iat": 1501533536,
	"exp": 1501537136,
	"jti": "ID.TsKlBQfGmiJcl2X3EuhzyyLfmzqi0OCd66rJ3Onk7FI",
	"amr": [
		"pwd"
	],
	"idp": "00o1zyyqo9bpRehCw1t7",
	"nonce": "c96fa468-ca1b-46f0-8974-546f23f9ee6f",
	"auth_time": 1501528157,
	"at_hash": "hEjyn3mbKjuWanuSAF-z4Q",
	"full_name": "Okta OIDC Fun"
}

full_name请注意id_token.

verifying-tokens

可以通过点击端点来验证访问令牌/introspect。对于active令牌,您会收到如下响应:

代码语言:javascript
复制
http --auth <OIDC Client ID>:<OIDC Client Secret> -f POST \
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/introspect \
token=eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNelJhOEV1elY5emgyREl6X3RVRUkifQ...
HTTP/1.1 200 OK
...

{
    "active": true,
    "aud": "https://afitnerd.com/test",
    "client_id": "xdgqP32nYN148gn3gJsW",
    "exp": 1498517509,
    "fullName": "Micah Silverman",
    "iat": 1498513909,
    "iss": "https://micah.oktapreview.com/oauth2/aus9vmork8ww5twZg0h7",
    "jti": "AT.JdXQPAuh-JTqhspCL8nLe2WgbfjcK_-jmlp7zwaYttE",
    "scope": "openid profile",
    "sub": "micah+okta@afitnerd.com",
    "token_type": "Bearer",
    "uid": "00u9vme99nxudvxZA0h7",
    "username": "micah+okta@afitnerd.com"
}

由于它需要 OIDC 客户端 ID 和密码,因此此操作通常会在可以安全拥有这些凭据的应用程序服务器中完成。您不希望最终用户 Web 或移动应用程序之类的东西访问 OIDC 客户端密钥。

如果token参数无效或过期,/introspect端点返回:

代码语言:javascript
复制
http --auth <OIDC Client ID>:<OIDC Client Secret> -f POST \
https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/introspect \
token=bogus
HTTP/1.1 200 OK
...
{
    "active": false
}

可以使用JWK端点验证 ID 令牌。JWK 是一种表示加密密钥的 JSON 数据结构。JWK 端点从用于 API 发现的 OIDC“知名”端点公开。这会返回很多信息。这是摘录:

代码语言:javascript
复制
http https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/.well-known/openid-configuration
HTTP/1.1 200 OK
...
{
    "authorization_endpoint": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/authorize",
	...
    "introspection_endpoint": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/introspect",
	...
    "issuer": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
    "jwks_uri": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/keys",
	...
    "userinfo_endpoint": "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/userinfo"
}

一些端点,例如/userinfo/authorize,现在应该看起来很熟悉了。我们感兴趣的是/keys中显示的端点jwks_uri

代码语言:javascript
复制
http https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7/v1/keys
HTTP/1.1 200 OK
...
{
    "keys": [
        {
            "alg": "RS256",
            "e": "AQAB",
            "kid": "cbkhWG0YmFsGiNO1LEkWSEszDCTNfwvJPpXxuVf_kX0",
            "kty": "RSA",
            "n": "g2XQgdyc5P6F4K26ioKiUzrdgfy90eBgIbcrKkspKZmzRJ3CIssv69f1ClJvT784J-...",
            "use": "sig"
        }
    ]
}

注意kid。它kid与我们的标头中的声明相匹配id_token

代码语言:javascript
复制
{
 "typ": "JWT",
 "alg": "RS256",
 "kid": "cbkhWG0YmFsGiNO1LEkWSEszDCTNfwvJPpXxuVf_kX0"
}

我们还可以看到使用的算法是RS256. 使用声明中找到的公钥n和安全库,我们可以确认 ID 令牌未被篡改。所有这些都可以在最终用户 SPA、移动应用程序等上安全地完成。

这是一个 Java 示例,它使用上面的声明jwks_uri来验证id_token: https: //github.com/dogeared/JWKTokenVerifier

代码语言:javascript
复制
java -jar target/jwk-token-verifier-0.0.1-SNAPSHOT-spring-boot.jar \
eyJhbGciOiJSUzI1NiIsImtpZCI6Ik93bFNJS3p3Mmt1Wk8zSmpnMW5Dc2RNel... \
g2XQgdyc5P6F4K26ioKiUzrdgfy90eBgIbcrKkspKZmzRJ3CIssv69f1ClJvT784J-... \
AQAB

Verified Access Token
{
  "header" : {
    "alg" : "RS256",
    "kid" : "cbkhWG0YmFsGiNO1LEkWSEszDCTNfwvJPpXxuVf_kX0"
  },
  "body" : {
    "ver" : 1,
    "jti" : "AT.LT9cRL_Kzd3T8Izw_ONZxHJ5xGBPD0m13iiEIDK_Nbw",
    "iss" : "https://micah.okta.com/oauth2/aus2yrcz7aMrmDAKZ1t7",
    "aud" : "test",
    "iat" : 1501533536,
    "exp" : 1501537136,
    "cid" : "0oa2yrbf35Vcbom491t7",
    "uid" : "00u2yulup4eWbOttd1t7",
    "scp" : [ "openid" ],
    "sub" : "okta_oidc_fun@okta.com"
  },
  "signature" : "ZV_9tYxt4v4bp9WEEDu038b7v_OHsbMZw13daR1s5_tI56oayBgJlnqf-..."
}

如果 JWT 的任何部分id_token被篡改,您将看到:

代码语言:javascript
复制
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

使用端点和使用 JWK 验证 JWT/introspect是 OIDC 的一个强大组件。它允许高度信任令牌没有以任何方式被篡改。并且,正因为如此,可以安全地强制执行其中包含的信息(例如到期)。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • OAuth2.0 OpenID Connect 三
    • OIDC 响应类型
      • OIDC 范围
        • 自定义范围和声明
          • verifying-tokens
          相关产品与服务
          云服务器
          云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档