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. 咱们还将运用隐式流,因为它会当即回来令牌。

鉴于此要求:

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。检查回来的令牌,咱们看到:

{
	"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的姿态:

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原始恳求的默许值。咱们将在规模部分检查一些更具体的呼应。

让咱们尝试另一个恳求:

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 标准的要求),其间编码了以下信息:

{
	"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 中。

最终咱们来看最终一种隐式流:

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以下内容:

{
	"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

让咱们尝试运用一切或许的(默许)规模类型的每个隐式流。

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,我得到了更多信息:

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隐式流程(仍然运用一切默许规模):

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这是编码到我回来的内容:

{
	"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

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

{
	"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终点,在这种情况下,咱们会得到:

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 的完成为此供给了支持。

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

OAuth2.0 OpenID Connect 三

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

OAuth2.0 OpenID Connect 三

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

{
	"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令牌,您会收到如下呼应:

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端点回来:

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“知名”端点公开。这会回来许多信息。这是摘录:

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

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

{
 "typ": "JWT",
 "alg": "RS256",
 "kid": "cbkhWG0YmFsGiNO1LEkWSEszDCTNfwvJPpXxuVf_kX0"
}

咱们还能够看到运用的算法RS256. 运用声明中找到的公钥n和安全库,咱们能够承认 ID 令牌未被篡改。一切这些都能够在终究用户 SPA、移动应用程序等上安全地完成。

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

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被篡改,您将看到:

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 的一个强壮组件。它允许高度信任令牌没有以任何方式被篡改。而且,正因为如此,能够安全地强制执行其间包含的信息(例如到期)。