# 使用 OIDC 授权
结合 Authing 实现 OIDC 授权的方法。
# 术语
End-User
:终端用户,也可以理解为使用您软件的人RP(Relying-Party)
:服务器后端OP(OIDC Provider)
: Authing 服务器
P.S. 文档中出现的 testapp.authing.cn
和 example.authing.cn
两个域名是可以在控制台配置的二级域名。
# 在 Authing 中创建一个用户池
使用 OIDC 需要先注册一个 Authing 账号 (opens new window)并新建一个用户池。
# 创建应用
注册完 Authing 账号并创建一个用户池之后,需要创建一个应用,然后你就可以使用 OIDC 协议完成用户登录和信息授权,创建应用请参考:
创建 OIDC 应用# OIDC 授权登录的基本流程
- 用户访问授权链接进行登录;
- 登录成功后回调到开发者配置好的 redirect_uri 中并在 URL query 附带相关参数;
- 如果返回类型是 code,那么开发者需要在后端使用 code 和 secret(用户创建完 OIDC 应用后会得到)换取 access_token;
- 如果返回类型是 id_token token,那么在用户登录成功后的回调 URI 中会直接附带 id_token 和 access_token;
- 在后端使用 access_token 可以换取用户信息(userInfo),完成身份认证;
如果你想直观的体验 OIDC 认证流程,请点击这里查看我们提供的示例 (opens new window)或点击这里可视化的理解 OIDC (opens new window)。
如果你对如何在后端处理 OIDC 有困惑,请参考 Github 上的示例代码:oidc-demo (opens new window)。
# 使用授权码模式(Authorization Code Flow)
授权码模式是 OIDC 授权登录中最常用的模式,OP 服务器返回一个授权码 code 给开发者后端服务器,在后端完成 code 换取 access_token,再用 access_token 换取用户信息的操作,从而实现用户的身份认证。
# 01 - 在控制台配置 OIDC 应用
进入控制台 > 应用 > 应用列表,找到你的应用,点击「配置」,在应用配置选项卡,找到授权模式,打开 authorization_code
模式,并在下方返回类型处选择 code
返回类型。
配置 OIDC 应用
# 02 - 发起登录请求
拼接一个链接并让终端用户在浏览器中访问,发起 OIDC 授权登录请求。
发起授权需要拼接一个用来授权的 URL,并让终端用户在浏览器中访问,具体参数如下:
请求示例:
https://<你的应用域名>.authing.cn/oidc/auth?client_id=5c9b079883e333d55a101082&redirect_uri=https://www.example.cn/example&scope=openid profile&response_type=code&state=52378542395
如需后续刷新 access_token,请按照以下方式拼接登录链接
带刷新 token 功能的登录请求示例:
https://<你的应用域名>.authing.cn/oidc/auth?client_id=5c9b079883e333d55a101082&redirect_uri=https://example.com&scope=openid profile offline_access&response_type=code&prompt=consent&state=235345
参考资料:
# 03 - 用户登录
发起 OIDC 登录之后,如果用户先前未在 OP 登录过,OP 会将用户重定向到登录页面,引导用户完成在 OP 的认证,此时用户需要选择一种方式进行登录:
你可以前往这个网址体验:https://sample-sso.authing.cn/login (opens new window)
用户登录
Authing 将验证此用户是否合法,验证通过后会将浏览器重定向到发起授权登录请求时指定的 redirect_uri 并通过 URL query 传递授权码 code 参数。
# 04 - 使用 code 换取 token
client_secret_post 方式换取 token
如果你在控制台配置 OIDC 时,换取 token 身份验证方式设置的为 client_secret_post,那么按照下面这种方法换取 token。
这里有完整的 nodejs 示例代码 (opens new window):
let code2tokenResponse;
try {
code2tokenResponse = await axios.post(
"https://<你的应用域名>.authing.cn/oidc/token",
qs.stringify({
code,
client_id: oidcAppId,
client_secret: oidcAppSecret,
grant_type: "authorization_code",
redirect_uri,
}),
{
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
);
} catch (error) {
ctx.body = error.response.data;
return;
}
使用 curl 发送请求示例:
curl --location --request POST 'https://<你的应用域名>.authing.cn/oidc/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'code=61yhuOVrgyhKlFTU~bnEKA_fnnz' \
--data-urlencode 'client_id=5e37979f7b757ead14c534af' \
--data-urlencode 'client_secret=64b517f8de3648091654eb4ee9b479d3' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'redirect_uri=https://baidu.com'
返回示例:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJqdGkiOiJQZU41YXg1b3FabGRhcUJUMzQzeUkiLCJzdWIiOiI1Y2U1M2FlYTlmODUyNTdkZDEzMmQ3NDkiLCJpc3MiOiJodHRwczovL29hdXRoLmF1dGhpbmcuY24vb2F1dGgvb2lkYyIsImlhdCI6MTU4MTQyMDk1NywiZXhwIjoxNTgxNDI0NTU0LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGF1dGhpbmdfdG9rZW4gZW1haWwgcGhvbmUgYWRkcmVzcyBvZmZsaW5lX2FjY2VzcyIsImF1ZCI6IjVkMDFlMzg5OTg1ZjgxYzZjMWRkMzFkZSJ9.rtpRSL3_U03zXShZUCILquSR_KEDuS-OldWpy8RLztWUNG_tMyrg2g9CG4hC7pJUwmgzZKtp7vsVrj6W0eyo_ehE4KGz9iKnyd46DFbx9W9pi-mieRW5HuVMGL2zvDH8zF467WXET2SVB3LUhFLNmEbxpvjPZ5Ksvbcd7nqHfnUN4-z3SqAvhGWWfcmt7QDFlLtWPw4LzyznEqmM9sDkNiNDnTkjmcjm7yHJR-yv5FvpzQB2kraQVOrrdAixbHf29ihOVO25CrjmgeKemg1vuLNGUcOrr_XWn7xaCSvyAfXrBuRalecW9RA4p_Cp6YslHc_572awekt3kUO2TebUQA",
"expires_in": 3597,
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJzdWIiOiI1Y2U1M2FlYTlmODUyNTdkZDEzMmQ3NDkiLCJiaXJ0aGRhdGUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImdlbmRlciI6IiIsImdpdmVuX25hbWUiOiIiLCJsb2NhbGUiOiIiLCJtaWRkbGVfbmFtZSI6IiIsIm5hbWUiOiIiLCJuaWNrbmFtZSI6IiIsInBpY3R1cmUiOiJodHRwczovL3VzZXJjb250ZW50cy5hdXRoaW5nLmNuL2F1dGhpbmctYXZhdGFyLnBuZyIsInByZWZlcnJlZF91c2VybmFtZSI6IiIsInByb2ZpbGUiOiIiLCJ1cGRhdGVkX2F0IjoiIiwid2Vic2l0ZSI6IiIsInpvbmVpbmZvIjoiIiwiY29tcGFueSI6IiIsImJyb3dzZXIiOiIiLCJsb2dpbnNfY291bnQiOjEwMywicmVnaXN0ZXJfbWV0aG9kIjoiZGVmYXVsdDp1c2VybmFtZS1wYXNzd29yZCIsImJsb2NrZWQiOmZhbHNlLCJsYXN0X2lwIjoiMTIxLjIxLjU2LjE3MSIsInJlZ2lzdGVyX2luX3VzZXJwb29sIjoiNWM5NTkwNTU3OGZjZTUwMDAxNjZmODUzIiwibGFzdF9sb2dpbiI6IjIwMjAtMDItMTFUMTE6MzU6MTUuNjk2WiIsInNpZ25lZF91cCI6IjIwMTktMDUtMjJUMTI6MDQ6NTguMjk0WiIsInRva2VuIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmtZWFJoSWpwN0ltVnRZV2xzSWpvaWRHVnpkRE5BTVRJekxtTnZiU0lzSW1sa0lqb2lOV05sTlROaFpXRTVaamcxTWpVM1pHUXhNekprTnpRNUlpd2lZMnhwWlc1MFNXUWlPaUkxWXprMU9UQTFOVGM0Wm1ObE5UQXdNREUyTm1ZNE5UTWlmU3dpYVdGMElqb3hOVGd4TkRJd09URTFMQ0psZUhBaU9qRTFPREkzTVRZNU1URjkuM0l0X0NJQTNFbUpoYWcyMW92WjNwd0RfY0owcTVTZkJjSURSZThRX3FoayIsImVtYWlsIjoidGVzdDNAMTIzLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicGhvbmVfbnVtYmVyIjoiMTMxMTIzNDEyMzQiLCJhZGRyZXNzIjoiIiwiYXRfaGFzaCI6IjV6QnNUOHF4RHc1RmNYdU55UFg4YUEiLCJzaWQiOiJkNmZiOTE5Ny00NmE3LTQ1ZGEtOGVkMC05ODhjZjg0ZjQwZWUiLCJhdWQiOiI1ZDAxZTM4OTk4NWY4MWM2YzFkZDMxZGUiLCJleHAiOjE1ODE0MjQ1NTQsImlhdCI6MTU4MTQyMDk1NywiaXNzIjoiaHR0cHM6Ly9vYXV0aC5hdXRoaW5nLmNuL29hdXRoL29pZGMifQ.VZzqULytIteyBfouww5TsHQ50gEhM06kUWMeDiO3FVFSCW9ys2bFPos5p6LFzliK4Ce09ypOwVQiRnE2gNYsukLvlUPlKDIP_Xk5W19frKi1Z8ImuIPvUqVMKbFutVNS0TfIPCPJVBl8C1j5OXeIs6z0V90QrvyJao6FqVEa3axOHxbhpo1fH2hP04-wkGOp_l10d7RFhGcnPyPnz9-C5X6A4UEsCSDCVw1mDQHxDSFP9OPaB_OlCG_Bi6G-CeLhPa3V5hyIefdBvxC9SIpK-6qY-_BfsNKkBHDVKMb0xodgN2hzn3UTUGBuuoiaB4JhCv72EZ7eiXKIXFz6zVcogA",
"refresh_token": "DuSPlrUFPAvCZ1WQKarv5MbEsXN",
"scope": "openid profile authing_token email phone address offline_access",
"token_type": "Bearer"
}
id_token 中会包含 scope 参数请求的信息,例如邮箱、手机号,解析后的 id_token:
{
"sub": "5f64afd1ad501364e3b43c1e",
"birthdate": null,
"family_name": null,
"gender": "U",
"given_name": null,
"locale": null,
"middle_name": null,
"name": null,
"nickname": null,
"picture": "https://usercontents.authing.cn/authing-avatar.png",
"preferred_username": "test1",
"profile": null,
"updated_at": "2020-09-27T06:06:29.853Z",
"website": null,
"zoneinfo": null,
"email": "test1@123.com",
"email_verified": false,
"phone_number": null,
"phone_number_verified": false,
"nonce": "EhoXn8m7vy",
"at_hash": "QVtna22pWzdkeSBW3f91pg",
"aud": "5f17a529f64fb009b794a2ff",
"exp": 1601468174,
"iat": 1601464574,
"iss": "http://console.xxx.localhost:3000/oidc"
}
client_secret_basic 方式换取 token
如果你在控制台配置 OIDC 时,换取 token 身份验证方式设置的为 client_secret_basic,那么按照下面这种方法换取 token。(client_secret_basic 是使用 HTTP Basic authentication 模式进行认证。)
其中 Authorization
请求头 Basic<空格>
后的值为 <client_id>:<client_secret>
的 base64 值。
none 方式换取 token
如果你在控制台配置 OIDC 时,换取 token 身份验证方式设置的为 none,那么换取 token 时无需传递 client_secret,其他参数和上表一样。
# 05 - 验证 access_token 和 id_token 的合法性
# 使用应用的 App Secret 检验
Authing 默认使用应用的 Secret 对 token 进行签名(也就是在创建应用时默认选择 HS256
算法)。
如果你使用 javascript
那么可以使用 jsonwebtoken 进行验证:
const jwt = require('jsonwebtoken');
let decoded = jwt.verify(token, <appSecret>);
如果是其他语言,那么你在服务端需要用 app_secret 作为 HS256 签名参数来计算签名和 JWT 中的签名进行对比,伪代码如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
"1133fd20c14e4cc29b6ecb71fb8eb952"// app_secret
)
# 使用公钥验证签名
如果使用 RS256 签名算法,需要使用公钥验证签名。Authing 将使用应用的私钥进行签名,请使用 https://<应用域名>.authing.cn/oidc/.well-known/jwks.json
中的公钥来验证签名。Authing 颁发的 access_token 和 id_token 都可以使用上述公钥进行验签。
如果你使用 javascript
,可以使用 jose
库来验证 RS256 签名:
const jose = require("jose");
// 下面的参数内容是将 https://<应用域名>.authing.cn/oidc/.well-known/jwks.json 返回的内容原封不动复制过来
const keystore = jose.JWKS.asKeyStore({
keys: [
{
e: "AQAB",
n:
"o8iCY52uBPOCnBSRCr3YtlZ0UTuQQ4NCeVMzV7JBtH-7Vuv0hwGJTb_hG-BeYOPz8i6YG_o367smV2r2mnXbC1cz_tBfHD4hA5vnJ1eCpKRWX-l6fYuS0UMti-Bmg0Su2IZxXF9T1Cu-AOlpgXFC1LlPABL4E0haHO8OwQ6QyEfiUIs0byAdf5zeEHFHseVHLjsM2pzWOvh5e_xt9NOJY4vB6iLtD5EIak04i1ND_O0Lz0OYbuV0KjluxaxoiexJ8kGo9W1SNza_2TqUAR6hsPkeOwwh-oHnNwZg8OEnwXFmNg-bW4KiBrQEG4yUVdFGENW6vAQaRa2bJX7obn4xCw",
kty: "RSA",
alg: "RS256",
use: "sig",
kid: "TfLOt3Lbn8_a8pRMuessamqj-o3DBCs1-owHLQ-VMqQ",
},
],
});
// 选项中 issuer 的内容是 https://<应用域名>.authing.cn/oidc,audience 的内容是 应用 ID
// id_token 很长,请向右滑动 ->
const res = jose.JWT.IdToken.verify(
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlRmTE90M0xibjhfYThwUk11ZXNzYW1xai1vM0RCQ3MxLW93SExRLVZNcVEifQ.eyJzdWIiOiI1ZjcxOTk0NjUyNGVlMTA5OTIyOTQ5NmIiLCJiaXJ0aGRhdGUiOm51bGwsImZhbWlseV9uYW1lIjpudWxsLCJnZW5kZXIiOiJVIiwiZ2l2ZW5fbmFtZSI6bnVsbCwibG9jYWxlIjpudWxsLCJtaWRkbGVfbmFtZSI6bnVsbCwibmFtZSI6bnVsbCwibmlja25hbWUiOm51bGwsInBpY3R1cmUiOiJodHRwczovL2ZpbGVzLmF1dGhpbmcuY28vdXNlci1jb250ZW50cy9waG90b3MvOWE5ZGM0ZDctZTc1Ni00NWIxLTgxZDgtMDk1YTI4ZTQ3NmM2LmpwZyIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QxIiwicHJvZmlsZSI6bnVsbCwidXBkYXRlZF9hdCI6IjIwMjAtMDktMzBUMDc6MTI6MTkuNDAxWiIsIndlYnNpdGUiOm51bGwsInpvbmVpbmZvIjpudWxsLCJlbWFpbCI6InRlc3QxQDEyMy5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBob25lX251bWJlciI6bnVsbCwicGhvbmVfbnVtYmVyX3ZlcmlmaWVkIjpmYWxzZSwibm9uY2UiOiJFNjViMVFvVVl0IiwiYXRfaGFzaCI6IkIzSWdPWUREYTBQejh2MV85cVpyQXciLCJhdWQiOiI1ZjE3YTUyOWY2NGZiMDA5Yjc5NGEyZmYiLCJleHAiOjE2MDE0NTM1NTgsImlhdCI6MTYwMTQ0OTk1OSwiaXNzIjoiaHR0cHM6Ly9vaWRjMS5hdXRoaW5nLmNuL29pZGMifQ.Z0TweYr9bCdYNJREVdvbJYcjXSfSsSNHBMqxTJeW-bnza0IIpBpEEVxlDG0Res6FZbcVzsQZzfJ9pj_nFgLjZxUUxv7Tpd13Sq_Ykg2JKepPf3-uoFqbORym07QEj4Uln0Quuh094MTb7z6bZZBEOYBac46zuj4uVp4vqk5HtCUSB4ASOAxwi7CeB1tKghISHz6PDcf6XJe_btHdzX1dparxtML-KvPxjpcHlt5emN88lpTAOX7Iq0EhsVE3PKrIDfCkG8XlL5y9TIW2Dz2iekcZ5PV17M35G6Dg2Q07Y_Apr18_oowOiQM5m_EbI90ist8CiqO9kBKreCOLMzub4Q",
keystore,
{
issuer: "https://oidc1.authing.cn/oidc",
audience: "5f17a529f64fb009b794a2ff",
}
);
console.log(res);
输出结果:
{
sub: '5f719946524ee1099229496b',
birthdate: null,
family_name: null,
gender: 'U',
given_name: null,
locale: null,
middle_name: null,
name: null,
nickname: null,
picture: 'https://files.authing.co/user-contents/photos/9a9dc4d7-e756-45b1-81d8-095a28e476c6.jpg',
preferred_username: 'test1',
profile: null,
updated_at: '2020-09-30T07:12:19.401Z',
website: null,
zoneinfo: null,
email: 'test1@123.com',
email_verified: false,
phone_number: null,
phone_number_verified: false,
nonce: 'E65b1QoUYt',
at_hash: 'B3IgOYDDa0Pz8v1_9qZrAw',
aud: '5f17a529f64fb009b794a2ff',
exp: 1601453558,
iat: 1601449959,
iss: 'https://oidc1.authing.cn/oidc'
}
# 将 token 或 id_token 发送到 Authing 提供的 token 验证接口进行验证
在线验证 access_token / id_token 合法性
Authing 提供了接口用于直接在线验证 access_token 或 id_token 的合法性。
参考链接
- JWKS 参考规范 (opens new window);
- 可以在线检验 JWT 的签名的网站:https://jwt.io (opens new window);
- RSA 公私钥 PEM 格式 与 JWK 格式互转:https://8gwifi.org/jwkconvertfunctions.jsp (opens new window);
- 生成 JWK:https://mkjwk.org (opens new window);
# 06 - 使用 access_token 换取用户信息
开发者应该在自己的后端服务器使用 access_token 换取用户信息。如果发起授权登录时的 scope 参数不同,这里的返回信息也会不同,返回信息中的字段取决于 scope 参数。字段符合 OIDC 规范 (opens new window),用户信息字段与 scope 对应关系请参考 scope 参数对应的用户信息。
使用 access_token 换取用户信息
请求链接示例:https://core.authing.cn/oidc/me?access_token=<access_token>
返回示例:
{
"sub": "5f7174df27e0eb9c6d21436d",
"birthdate": null,
"family_name": null,
"gender": "U",
"given_name": null,
"locale": null,
"middle_name": null,
"name": null,
"nickname": null,
"picture": "https://usercontents.authing.cn/authing-avatar.png",
"preferred_username": null,
"profile": null,
"updated_at": "2020-09-28T05:33:15.892Z",
"website": null,
"zoneinfo": null
}
更多字段解释请参考用户信息字段含义。
# 07 - 刷新 token
使用 refresh_token 刷新用户的 access_token 和 id_token。
如需使用刷新 token 功能,需要进入控制台 > 应用 > 应用列表,找到你的应用,点击「配置」,在应用配置中勾选 refresh_ token。
refresh_token
注意 ⚠️⚠️⚠️:发起登录请求时必须填写正确的 URL query 参数,只勾选 refresh_token
而授权登录参数错误或者直接输入应用认证地址,Authing 不会返回任何 refresh_token。scope 参数中必须有 offline_access,prompt 参数值必须为 consent。能够返回 refresh_token 的登录请求示例:
https://example.authing.cn/oidc/auth?client_id=5c9b079883e333d55a101082&redirect_uri=https://example.com&scope=openid profile offline_access&response_type=code&prompt=consent&state=235345
# 08 - 撤回 token
08 - 撤回 token
只有 access_token 和 refresh_token 可以被撤回,id_token 无法撤回。
# 09 - 检验 token 状态
09 - 检验 token 状态
只有 access_token 和 refresh_token 可以检测状态,id_token 无法检测。
# 使用隐式模式(Implicit Flow)
OIDC 隐式模式不会返回授权码 code,而是直接将 access_token
和 id_token
通过 URL hash 发送到回调地址前端,后端无法获取到这里返回的值,因为 URL hash 不会被直接发送到后端。
# 在控制台配置 OIDC 应用
进入控制台 > 应用 > 应用列表,找到你的应用,点击「配置」,在应用配置选项卡,找到授权模式,勾选 implicit
模式,并在下方返回类型中勾选 id_token token
和 id_token
。
id_token token / id_token
# 发起授权
发起隐式模式的授权登录需要拼接一个 URL,并让终端用户在浏览器中访问,不能直接输入认证地址域名。具体参数如下:
参数名 | 意义 |
---|---|
client_id | 应用 ID。 |
redirect_uri | 回调链接,用户在 OP 认证成功后,OP 会将 id_token、access_token 以 URL hash 的形式发送到这个地址。这个值必须出现在控制台配置的回调地址中,否则 OP 不允许向该地址回调。启用隐式模式时,控制台配置的所有 redirect_uri 建议使用 https 地址,否则 access_token 将会在明文状态下传输,造成安全隐患。如果你要使用 http 地址,请在控制台打开「不强制 implicit 模式回调链接为 https」开关。 |
scope | 需要请求的权限,必须包含 openid。如果需要获取手机号和 email 需要包含 phone email;多个 scope 请用空格分隔。同时 id_token 中会包含相关的字段。隐式模式不支持返回 refresh_token,所以 offline_access 字段无效。 |
response_type | 返回类型,可以为 id_token, id_token token。用于指定 OP 返回 id_token 和 access_token。参考 OIDC 规范 (opens new window)。 |
prompt | 可以为 none,login,consent 或 select_account,指定 OP 与 End-User 的交互方式。参考 OIDC 规范 (opens new window)。 |
state | 一个随机字符串,用于防范 CSRF 攻击,如果 response 中的 state 值和发送请求之前设置的 state 值不同,说明受到攻击。 |
nonce | 一个随机字符串,用于防范 Replay 攻击,implicit 模式下必填。 |
假设你创建了一个域名为 example
的 OIDC 应用,那么发起隐式模式 OIDC 授权登录的网址是:
GET https://example.authing.cn/oidc/auth?client_id=5ca765e393194d5891db1927&redirect_uri=https://example.com&scope=openid profile&response_type=id_token token&state=6223573295&nonce=1831289
# 获取 id_token 和 access_token
id_token、access_token 会以 URL hash 的形式传递,跳转后链接示例:
https://authing.cn/#id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1Y2QwMjZlZjNlZDlmOTRkODBmZTM2YWUiLCJub25jZSI6IjE4MzEyODkiLCJzaWQiOiI4YzgzN2I5My01OTNlLTQzZjctYWMzNC0yYjRmZDU3ZGFhMTciLCJhdF9oYXNoIjoiVFFtbFlEVTVPZGF1Zjl0U0VKdHY5USIsInNfaGFzaCI6Ind3SDNXclV2b0hiSUp5TWVZVHU4bHciLCJhdWQiOiI1ZDAxZTM4OTk4NWY4MWM2YzFkZDMxZGUiLCJleHAiOjE1NjA0MDkzNjgsImlhdCI6MTU2MDQwNTc2OCwiaXNzIjoiaHR0cHM6Ly9vYXV0aC5hdXRoaW5nLmNuL29hdXRoL29pZGMifQ.T9M0s6rk4Teq6VOOBRIElgHK9KyM3q0ZJj2aS0VD_Fw&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3OE9XcVJNVXJEUXpMMXpHVzVtUWoiLCJzdWIiOiI1Y2QwMjZlZjNlZDlmOTRkODBmZTM2YWUiLCJpc3MiOiJodHRwczovL29hdXRoLmF1dGhpbmcuY24vb2F1dGgvb2lkYyIsImlhdCI6MTU2MDQwNTc2OCwiZXhwIjoxNTYwNDA5MzY4LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIiwiYXVkIjoiNWQwMWUzODk5ODVmODFjNmMxZGQzMWRlIn0.mR0MZDwlZWGRMsAZjQ27sDFFqYoDgZ6WHTK4C7JbML4&expires_in=3600&token_type=Bearer&state=jazz&session_state=26ec053be9f47d68dc430f84b97efb1095469fe10169a9e00ef4092718714b8b
换取用户信息的流程和授权码模式相同。
为什么信息在 URL hash 里而不是 query 里?因为 hash 内容不会直接发送到服务器,避免 id_token、access_token 被盗用。
参考资料:
# 使用混合模式(Hybrid Flow)
混合模式支持同时将 code
、access_token
和 id_token
返回到回调地址前端,都以 URL Hash 的形式传递,后端无法直接接收到,因为 URL hash 不会直接发送到服务器。
# 在控制台配置 OIDC 应用
进入控制台 > 应用 > 应用列表,找到你的应用,点击「配置」,在应用配置选项卡,找到授权模式,选择 authorization_code
和 implicit
,并在下方返回类型勾选 code id_token token
、code id_token
、code token
。
code、access_token 和 id_token
# 发起授权
发起授权需要拼接一个用来授权的 URL,并让终端用户在浏览器中访问,不能直接访问认证地址域名。具体参数如下:
参数名 | 意义 |
---|---|
client_id | 应用 ID。 |
redirect_uri | 回调链接,用户在 OP 认证成功后,OP 会将 code、id_token、access_token 以 URL hash 的形式发送到这个地址。这个值必须出现在控制台配置的回调地址中,否则 OP 不允许向该地址回调。使用混合模式时,控制台配置的所有 redirect_uri 建议使用 https 地址,否则 access_token 将会在明文状态下传输,造成安全隐患。如果你要使用 http 地址,请在控制台打开「不强制 implicit 模式回调链接为 https」开关。 |
scope | 需要请求的权限,必须包含 openid。如果需要获取手机号和 email 需要包含 phone email,多个 scope 请用空格分开。同时 id_token 中会包含相关的字段。如果需要刷新 token,需要有 offline_access 参数,同时 response_type 参数中必须包含 code,并使用 code 换取 token,在后续 code 换 token 的结果中才会返回 refresh_token。 |
response_type | 返会类型,此处为 code id_token token。用于指定 OP 返回 code、id_token 和 access_token。参考 OIDC 规范 (opens new window)。 |
prompt | 可以为 none,login,consent 或 select_account,指定 OP 与 End-User 的交互方式。参考 OIDC 规范 (opens new window)。 |
state | 一个随机字符串,用于防范 CSRF 攻击,如果 response 中的 state 值和发送请求之前设置的 state 值不同,说明受到攻击。 |
nonce | 一个随机字符串,用于防范 Replay 攻击,混合模式下必填。 |
假设你创建了一个域名为 example
的 OIDC 应用,那么授权网址是:
GET https://example.authing.cn/oidc/auth?client_id=5ca765e393194d5891db1927&redirect_uri=https://example.com&scope=openid profile&response_type=code id_token token&state=23758235&nonce=1831289
code、access_token 和 id_token 通过 URL hash 传递,跳转后链接示例:
https://example.com/#code=pIY83Jl_bcerNN9Wt57Sq0TAjTr&id_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJzdWIiOiI1YzlmNzVjN2NjZjg3YjA1YTkyMWU5YjAiLCJub25jZSI6IjE4MzEyODkiLCJzaWQiOiIxOTdlOGExMy0wMzE4LTRkZDEtYjQ3Mi0xZjI0MDk5ZTUzOWYiLCJhdF9oYXNoIjoiUFlXaTFER29jRlotYmlYd0d5WXlpZyIsImNfaGFzaCI6Ik4yUmkyUFpidktYdXRmdGhZbUhrM2ciLCJzX2hhc2giOiJ3d0gzV3JVdm9IYklKeU1lWVR1OGx3IiwiYXVkIjoiNWNhNzY1ZTM5MzE5NGQ1ODkxZGIxOTI3IiwiZXhwIjoxNTU0NjE1NjcyLCJpYXQiOjE1NTQ2MTIwNzIsImlzcyI6Imh0dHBzOi8vYXV0aGluZy5jbiJ9.a--JC_6CyUi0Z7z3DCKT51wJkKT7MmtlVHhrNujhxHCfgQqzqS3wMxVj6oEe_cfjVQNgJ-Xe1oiL8uMAxVN-cM1Ra1JQcavUujua2IxxtG4Nkh84rTukqsrPfuNhNO7MRP6Fa9qIIdKeKkQKyh1zBKE6322zK_ECdfGd2sWdqqXiQyJXg6ODhPZDidsGuluV3bZiAY3brMSMmh6QC99StOP5ZwSKtlRMyYE3MIRWsQ4W2HkHBrk67T_scQ6XN6mdBKi2OZW-E7fXeyVwH-ibWDzlUpmFSaj3a-WbkDe3nfCv8MHj439aJNU-AXfIgLsckvCO5_dJOUWGHg6hemT9bw&access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJqdGkiOiIxUzgyaUtSdXFlWW1DUmFrMFl1S0kiLCJzdWIiOiI1YzlmNzVjN2NjZjg3YjA1YTkyMWU5YjAiLCJpc3MiOiJodHRwczovL2F1dGhpbmcuY24iLCJpYXQiOjE1NTQ2MTIwNzIsImV4cCI6MTU1NDYxNTY3Miwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSIsImF1ZCI6IjVjYTc2NWUzOTMxOTRkNTg5MWRiMTkyNyJ9.tHwxiH5QXXA46Y4mIwcBck3uDArMj5TMGEBAQ8Eeln6oFbwBY3aS5cSV6e3anZDwKZrdgrdFlyj9-Bl1T5V1rNJK-Xz_aFnM6XxyO1jSHcn-6KXGwmz68D50VIHior39cuoj9OXbNCei5RVghjh2cRT3SenYki7UeJBgmfQA6l2aZZpBrn9aphXr9OoPS47T59I0Ynn2yMIYIMDOX7hh8E5oV1hrK3hyjAvp3ghmzyRfj2BlG9rBo1hd_d5E8x6OIzNdvPKXwVASJZRxov2Dx0ma36zxzSObyXgCloUv2KlbmL9-Wj8d3H6FhHC75DLfJYx-uRgNqW7CFKGeRkPjkQ&expires_in=3600&token_type=Bearer&state=jazz&session_state=101666b6b70cfb4406ad9c0c906039de39776140e66e48acdb63ab8acb309701
换取用户信息的流程和授权码模式相同。
参考资料:
# 使用 Password 模式
不推荐使用此模式,尽量使用其他模式。
# 在控制台配置 OIDC 应用
进入控制台 (opens new window) > 应用 > 应用列表,找到你的应用,点击「配置」,在应用配置中勾选 password。点击「保存」。
password
使用登录凭据换取 token
在 Password 模式中,可以直接使用用户的登录凭据换取 OIDC token
用户标识字段「email、phone、username」是互斥的,你不能同时传入其中任何两个或以上的参数组合。
参考资料
# 使用 authingToken 模式(不推荐)
该模式现在仅限于兼容老版本 Authing 用户,你不应该使用这个模式。
# 在控制台配置 OIDC 应用
进入控制台 (opens new window) > 应用 > 应用列表,找到你的应用,点击「配置」,在应用配置中勾选 authingToken。点击「保存」。
authingToken
使用 Authing Token 换取 OIDC token
在 authingToken 模式中,可以直接使用用户的 Authing Token 换取 OIDC token。
# 退出 SSO
如果你使用了 OAuth 2.0、OIDC 或 SAML 实现了单点登录,可以访问以下地址让用户在 OP 登出:
https://<你的域名>.authing.cn/login/profile/logout?redirect_uri=<退出之后的回调地址>
redirect_uri
是退出后你想要返回的地址。
如果你想要在后端退出 SSO,可以自行维护一个 Cookie - Session 的状态,然后设置 Cookie 过期即可。
# 接下来你可能需要
OIDC 常见问题← 创建 OIDC 应用 理解 OIDC 流程 →