1. 背景介绍
IDaaS 采用标准的 OIDC 协议授权码模式来支持常规企业自研应用接入。
如果您对接过微信登录,会发现对接 SSO 和对接微信扫码登录是一个道理。钉钉、微信等社交身份均采用 OAuth 协议实现扫码登录。IDaaS 采用的 OIDC 协议是 OAuth 协议的升级版。
兼容 OAuth:OIDC(OpenID Connect) 1.0 协议在 OAuth2.0 协议之上建立了用户身份层,OIDC 协议也因此兼容 OAuth2.0 协议。OIDC 授权码模式和 OAuth2.0 授权码模式流程一致,区别是 OIDC 对用户信息端点进行了标准化,并在 Token 端点会返回用户的 ID Token。
授权码流程介绍
自研应用 SSO 对接采用 OIDC 授权码模式。
您的应用只需要完成与 IDaaS 之间的两个接口交互(授权端点、令牌端点),即可完成 SSO 的主体流程。
登录将由 IDaaS 完全托管,您的应用只需解析登录结果即可。
2. 对接 SSO
2.1. 创建自研(或 OIDC 协议)应用
您需要在 IDaaS 中创建一个自研应用或标准协议(OIDC)应用,并获取应用密钥。若您已经获取到,可以跳过这个步骤。
请管理员参考 3. 自研应用 完成应用创建。
在该应用的【通用配置】标签中,即可获取到 client_id 和 client_secret。这对密钥将用于后续接口请求。
若希望对密钥进行管理或替换,请参考应用的 基本配置。
2.2. 请求授权端点 Authorization Endpoint
下面步骤需要应用开发者处理。
在用户尝试访问您的应用时,应用需要判断当前是否有可用的已登录身份。
若用户需要登录,您需要向 IDaaS 发起授权登录的请求。您可以在【应用管理】【登录访问】标签中,下方的【应用配置信息】中获取应用的授权端点。
请参照如下示例,在授权端点地址的基础上,拼装出完整的授权请求访问地址,并在浏览器中发起 302 跳转。
{{授权端点 Authorization Endpoint}}?
client_id=app_***&
redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F***&
response_type=code&
scope=openid&
state=525f49cc-***
字段 | 必填 | 示例 | 说明 |
client_id | 是 | app_michs7r****6pye | 上一步骤获取到的 client_id。 |
scope | 是 | openid email profile | 自研应用默认配置为 openid email profile,意味着应用可获取到已登录账户的 ID、用户名、邮箱信息。若您使用 OIDC 标准模板应用,管理员还可在配置页选择 phone。scope 对应的字段范围,可见文档末尾说明。 |
response_type | 是 | code | 此值固定为 code,代表采用授权码模式。 |
redirect_uri | 是 | http://localhost:3000/user/oauth2/aliyunidaas/callback | 用户登录完成后,IDaaS 向应用返回登录结果的重定向地址。该地址应该为应用接收 IDaaS 参数的中继地址,并能接收授权码 |
state | 否 | 525f49cc-87c4-4655-b79c-4c4f971b1ad1 | state 是由应用生成的随机字符串,建议长度 32 位以上。该 state 值会在后续步骤返回给应用,应用届时应验证 state 值是否与发起传入的一致,以确保同一会话,以规避 XSRF 安全漏洞。非必填,但强烈建议填写。 |
2.3. 用户自助登录
若请求顺利,向应用授权端点的请求,会跳转到 IDaaS 登录页。
用户可以通过任意已配置的登录方式,完成身份验证。IDaaS 提供了多样的、不同安全级别的登录能力,包括钉钉扫码登录、短信登录等。详情参考:登录方式。
登录成功后,浏览器会 302 跳转回应用指定的 redirect_uri,并在 URL 参数中携带 code 和 state 参数。
参考示例:
{{redirect_uri}}?
code=CO***&
state=525f49cc-***
字段 | 示例 | 说明 |
code | COE59pkCTm4A9nmowJUsfsfarGEaiShj3TuDc7NCzLCYu9 | 即授权码。在获取令牌的请求中使用。 |
state | 525f49cc-87c4-4655-b79c-4c4f971b1ad1 | 应用接收到后,应确保与调用授权端点时传入的 |
2.4. 请求令牌端点 Token Endpoint
上一步接收到 code 授权码后,应用应使用获得的 code 向令牌端点(Token Endpoint)发起 POST 请求。
与上述授权端点一样,令牌端点也可以在【应用管理】【登录访问】标签下方的【应用配置信息】中获取到。
请求示例如下:
POST /v2/<instance_id>/<app_id>/oauth2/token HTTP/1.0
Host: eiam-api-cn-hangzhou.aliyuncs.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=n0esc3N*****5acc3f0ogp4
&client_id=s6BhdR*****kqt3
&client_secret=7Fjfp0ZBr1*****KtDRbnfVdmIw
&redirect_uri=http%3A%2F%2Fwww.example.com%2Fsso%2Fcallback
字段 | 必填 | 示例 | 说明 |
grant_type | 是 | authorization_code | 固定填写 authorization_code。 |
code | 是 | n0es***5acc3f0ogp4 | 即上一步中返回的授权码 code。 |
client_id | 是 | app_mihar***s3rj7r4e4 | IDaaS 默认支持两种接口认证方式,以验证调用方可信。 在 若您需要使用另一种 |
client_secret | 是 | CSAuycr***vtqRozS1V1 | 同上。 |
redirect_uri | 是 | http%3A%2F%2Fwww.example.com%2Fsso%2Fcallback | 即上一步使用的 redirect_uri,按照协议要求,必须重新传入以保障请求连续性。 |
响应结果如下:
{
"token_type": "Bearer",
"access_token": "ATM4SoVDqWgUJHLu3Bg6qF2hccE6cvjKXiKdiJ2Dc8RJZSbzpBDXPZK3gPhGxQs16s3s7MsZ46fEyiYTWG7EGFKi9uzGjRALaRLecPutBLzzQQRVUt6pbuarCbq5hFRje6bzsrW4jTehhCtZM5JneEfcSQ2ViSDVZGNNtMKAA6v7kTeubZrTaWNzosNMyzGXoD4rqPBwF9FsYqwACQ4aJrt9NnS3NpgDKoMtqEQs5TfDsCYMKYmp7Z73F2BJz89jzN1utEbnuj3HnvyRQPCismDiXjS8EPvoUZBrUBMhrnzYmMcT9KmzKoC12sQjDRQYqgPVxQyMKwQKwwHWXV7stEXnoSt524GW8HVrF3WRsM2N1Ykod1irCz7ZasSwk3ZS5mtn6fcSp8NH8",
"expires_in": 1200,
"expires_at": 1644843164,
"id_token": "eyJraWQiOiJLRVkyVHkxcUw2dTIxTkdLbWNjdjNqd2ZkMm5kbWd0UVBuYWciLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX3V5dmVmb3RqbjdrcGJlamZteG9vczNydG1tIiwianRpIjoiand0X2FhYWFjN3h5aGNsYWM2YXFrZ3RqYXhzdGh3NXlvdG41ZDc3cG1raSIsImlzcyI6Imh0dHBzOi8vcHJlLWVpYW0tYXBpLWNuLWhhbmd6aG91LmFsaXl1bi1pbmMuY29tL3YyL2lkYWFzX2JxZ2xkdnpwcGEyYWw2aTZhYzVxemphcWpxL2FwcF9taHlsZ28zaWFpcmpxamR4NWVvcDZ1YWYzNC9vaWRjIiwiaWF0IjoxNjQ0ODQxOTY1LCJuYmYiOjE2NDQ4NDE5NjUsImV4cCI6MTY0NDg0MjI2NSwiYXVkIjoiYXBwX21oeWxnbzNpYWlyanFqZHg1ZW9wNnVhZjM0IiwiYXRfaGFzaCI6IlhIRWFHcE1vb005enZRWGFNekNORUEifQ.abebHwoSzOi92-QOKO_E38jyfxjzVLpRIK858UsOehe_GzBoKOEl1zQOSljBB7CwZCdQJpqI1rUxqQopwjvHRSfA-O4_cc4sXDpZYXodeVRXUiv1kYB1b4gZ-hStcE1eh_5jJj1dpoGPBsjTTHjp43EgDx1-8M-8ePF3zXZAfqxCjjroGgB9qXtSreRAIUh5ODViyHYRSAis7CNdP7jKG1dU1UNSGwXWNyRcgVaCqL05gCh0LhHrutMXDy8pcKzdXHQMMBaHF-rGkkGdlp4q9KqwjkpzakcWieRmPa2UUXLdQgK1Pgzc5F7mE-fvsvVfMYfh_JgRIadj-frOIRFChA"
}
至此,您的用户已经成功完成登录。您可以选择以下两种方式,进一步获取当前登录身份信息,并完成应用侧登录态的创建:
使用响应结果中的 id_token,经过验证后,直接拿到用户标识。
使用响应结果中的 access_token,调用 IDaaS 用户端点,获取当前已登录用户信息。
具体获取方式请参考下面章节。
可以获取到的用户数据范围,由第一步授权端点请求中的 scope 参数指定。
2.5. 通过程序解析 id_token
id_token
是一个包含身份信息的签名令牌信息(JWT 格式)。IDaaS 签发的 id_token 中,包含着解码即明文可见的用户数据,以及签名 Signature。
为了方便理解,您可以将 id_token
完整内容,粘贴到 https://jwt.io 网站中,查看其包含的内容。
内容示例如下:
{
"kid": "KEY2Ty1qL6u21NGKmccv3jwfd2ndmgtQPnag",
"alg": "RS256"
}.{
"sub": "user_uyvefotjn7kpbejfmxoos3rtmm",
"jti": "jwt_aaaac7xyhclac6aqkgtjaxsthw5yotn5d77pmki",
"iss": "https://pre-eiam-api-cn-hangzhou.aliyun-inc.com/v2/idaas_bqgldvzppa2al6i6ac5qzjaqjq/app_mhylgo3iairjqjdx5eop6uaf34/oidc",
"iat": 1644841965,
"nbf": 1644841965,
"exp": 1644842265,
"aud": "app_mhylgo3iairjqjdx5eop6uaf34",
"at_hash": "XHEaGpMooM9zvQXaMzCNEA",
"name": "testuser",
"preferred_username": "testuser",
}.[Signature]
在使用其中内容进行应用登录前,您需要对签名 [Signature] 进行验证,以确保令牌是 IDaaS 签发,而非任何其他三方,以保障登录的安全性。这一安全性是必须的。
2.5.1. 获取验签公钥
在进行签名验证之前,需要首先获得 IDaaS 公开的验签公钥端点。
仍然在【应用管理】【登录访问】标签下方的【应用配置信息】中,我们可以看到应用的验签公钥端点。
应用可通过访问这一端点,获取到当前的公钥信息。您也可以直接将此地址在浏览器中打开,即可展示公钥信息。
示例如下:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "KEYkYnc55GJjD6y7VeCTvT7So44RGDYdbfs",
"n": "pXmYkIpy1vaNjTMclU86BQjfmDhjlqMAX8ySVvh9gO-nae4ayvG_aCRYQL3qGCpFLZYrG3Jjoa0ktCn8PTTRi-v4gP27T7u6bUy0GXTlh3eKE0v1LYB81nfqjF2uazlPwPR5yYOhhWcK-gMNByLfE3CnkDc1YGwA3dZmIz-ZjOCKy8xLaBuqjrvwn5tpMpAoYEEaH4jIm7unTdhbKEKspNR-UXKD8q9RppMh5Tn2sB6oPHlQANudJDgqSwEOevIrdmHU0Zqxrb9cscGH9hH0QjmYEu72yI8BVeliPo3jK6JIoqCIcj5K_t8BJlFQ9QLJ8_o9tmd3BFv5_LVsh4BKGw"
}
]
}
接下来您即可以通过代码完成验签并获取 id_token
中的内容了。
2.5.2. 验签并登录
您可以通过 https://jwt.io/libraries 找到对应语言的工具,并在代码中使用工具对 id_token
进行验签与解析。
下面以 Java 库:org.bitbucket.b_c:jose4j 为例。
首先加入对应的 Maven 依赖:
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.12</version>
</dependency>
代码示例如下:
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
public class IdTokenTest {
public static void main(String[] args) throws Exception {
String issuer = "https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/app_mkif4dwlpeh6dns4pxpzbasqmu/oidc";
String appId = "app_mkif4*****pxpzbasqmu";
// 请参照如下方式,设定解析用的应用公钥
String jwkJson = "{\n"
+ " \"keys\": [\n"
+ " {\n"
+ " \"kty\": \"RSA\",\n"
+ " \"e\": \"AQAB\",\n"
+ " \"use\": \"sig\",\n"
+ " \"kid\": \"KEY2H82C2at57itnW4onT3p1ySjwH4nirjCk\",\n"
+ " \"n\": \"w7Jl3fAUJp_9GuxV*****QsOA4lnXR5OD4kF4QbIeBiDiH8_MThrFi9k2MB6YMkSzf5JfIkpAS3JCqZ7k6Wooydp4pzaZNZAk3SGzdsa022RmAT"
+ "-Iayi4Yj6J9tSdTQCjwh2XkzzsIxA_Hla8rWiQ8Vhw1"
+
"-7QArgObfe67nSR7LxD55MFLxk9FU0*****RlGhrQGE_0LUuGWtCJG1r1e6aKquyswfxxAr3Rvj8QGIeJrG0R1Pv8m8d1_5OdULhB7149VqjM6D98WFjab0U2SNv0UlREZXTcS4p-2QNm_1egYRRpJEY_00FZqNSYsmErMGepYhO_61KoGqd8cphWQ\"\n"
+ " }\n"
+ " ]\n"
+ "}";
String jwt = "eyJraWQiOiJLRVkySDgyQzJhdD*****uaXJqQ2siLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2Vy*****lmNjRjZjR3amFrbnBieGpjd3V1IiwianRpIjoiand0X2FhYWFkYWllYTc2eWg1cW0zcm11bnoyeGg0eHd5aTJzZHBoNjR6aSIsImlzcyI6Imh0dHBzOi8vZWlhbS1hcGktY24taGFuZ3pob3UuYWxpeXVuY3MuY29tL3YyL2lkYWFzX3BhZHlybHV4M21waHJsc2V4NHVvbnlxaHh1L2FwcF9ta2lmNGR3bHBlaDZkbnM0cHhwemJhc3FtdS9vaWRjIiwiaWF0IjoxNjUzNjMwMDQxLCJuYmYiOjE2NTM2MzAwNDEsImV4cCI6MTY1MzYzMDM0MSwiYXVkIjoiYXBwX21raWY0ZHdscGVoNmRuczRweHB6YmFzcW11IiwibmFtZSI6InRlc3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwidXBkYXRlZF9hdCI6MTY1MzYyODU5MH0.pAsUNB8OkdpIxJMZRfLJ7Pa31tsJyl44a1jVIlvdQxwOtPULAwrFxnB0X3eQx89hUGCdvYWl9FO9o-5kT7L-RER0wJYz9YNKqrVNBnaRwINRZyeYLRVurWMMzODQz-V0ULd9raM1M_i2f_SoWFs1gPFtYh_ijUARHISi7Q3q93ZfAuY8Lq2Nq07QunmDbosvioUd5wJG7WCxW5XXZYDUQe9p5IEYd1MSvnWuTOLbg7rKn0Vm4dNYGWjz1WuoAyCsc_QxOCqpmQ_2czoqPeN-SvPJAQ2CykLk7DSnGpABw1aNrjDidLS9Beqsga9VDCth86sk_0lyTZOaORtUrfVTtQ";
JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson);
JwtConsumer jwtConsumer = createJwtConsumer(jsonWebKeySet, issuer, appId);
JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
// 已经验签完成,打印输出 id_token 中包含的用户信息
System.out.println(jwtClaims);
}
// 验签工具方法
public static JwtConsumer createJwtConsumer(JsonWebKeySet jsonWebKeySet, String issuer, String appId) {
final JwtConsumerBuilder jwtConsumerBuilder = new JwtConsumerBuilder();
jwtConsumerBuilder.setExpectedIssuer(issuer);
jwtConsumerBuilder.setRequireIssuedAt();
jwtConsumerBuilder.setRequireExpirationTime();
jwtConsumerBuilder.setAllowedClockSkewInSeconds(60);
jwtConsumerBuilder.setExpectedAudience(appId);
jwtConsumerBuilder.setVerificationKeyResolver((jws, nestingContext) -> {
final String signKeyId = jws.getKeyIdHeaderValue();
for (JsonWebKey jsonWebKey : jsonWebKeySet.getJsonWebKeys()) {
if (signKeyId.equals(jsonWebKey.getKeyId())) {
return jsonWebKey.getKey();
}
}
throw new RuntimeException("Cannot find verification key: " + signKeyId);
});
return jwtConsumerBuilder.build();
}
}
输出示例如下:
JWT Claims Set:{sub=user_dt6kj6yf64cf4wjaknpbxjcwuu,
jti=jwt_aaaadaiea76yh5qm3rmunz2xh4xwyi2sdph64zi,
iss=https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_padyrlux3mphrlsex4uonyqhxu/app_mkif4dwlpeh6dns4pxpzbasqmu/oidc,
iat=1653630041,
nbf=1653630041,
exp=1653630341,
aud=app_mkif4dwlpeh6dns4pxpzbasqmu,
name=test,
preferred_username=test,
updated_at=1653628590
}
由此获取到 IDaaS 中已登录身份信息,应用可用其顺利登录。
2.6 通过UserInfo端点获取用户信息
除了可以通过解析 id_token 获取用户信息外,还可以通过 UserInfo 端点(用户信息端点)获取用户信息。
仍然在【应用管理】【登录访问】标签下方的【应用配置信息】中,我们可以看到应用的验签公钥端点。
UserInfo请求遵循标准 RFC6750,请求示例如下:
GET /v2/<instance_id>/<app_id>/oauth2/userinfo HTTP/1.0
Host: eiam-api-cn-hangzhou.aliyuncs.com
Authorization: Bearer <AccessToken>
返回参数示例:
{
"sub": "user_dt6kj6yf64cf4wjaknpbxjcwuu",
"name": "test",
"preferred_username": "test",
"updated_at": 1653899948
}
UserInfo 端点返回的业务字符与 id_token 中的字段是保持一致的,即在 “扩展 id_token” 中配置的字段也会在 UserInfo 端点中返回。
3. 其他高级设置
若您对 OIDC 协议有深入了解,您可能会用到下列概念或能力,供参考。
3.1. OIDC Discovery 应用发现端点说明
OIDC 应用的 issuer
是对令牌发行方(即 IDaaS)的唯一标识,格式如下:
https://<idaas-api-domain>/v2/<instance_id>/<application_id>/oidc
尖括号中的参数如下:
字段 | 说明 | 示例 |
idaas-api-domain | 用户门户地址 | https://nfaaacn.aliyunidaas.com |
instance_id | 实例 ID | idaas_maaaaaaaaaaar2ed22e6m |
application_id | 应用 ID | app_maaaaaaaaaaaaaaajy6rbau |
IDaaS 支持 OpenID Connect Discovery 1.0 标准,在 issuer
后再加上 /.well-known/openid-configuration
就是该应用的 OIDC 发现端点地址。
通过请求发现端点,您可以自动发现如下端点信息。所有的请求端点都可以直接从【应用配置信息】中获取。
端点 | 说明 |
authorization_endpoint | 授权端点 |
device_authorization_endpoint |
|
token_endpoint | 令牌端点 |
revocation_endpoint | 令牌吊销端点 |
userinfo_endpoint | 用户信息端点 |
jwks_uri | JWK公钥端点 |
3.2. Scope 与字段权限的对应关系
OIDC 中 id_token 中包含的用户信息与 scope 的对应关系如下:
字段 | scope | 说明 |
sub | openid | 用户的 |
jti | openid | JWT 令牌ID,辅助字段 |
iss | openid | JWT 签发的 |
iat | openid | JWT 签发时间,辅助字段 |
nbf | openid | JWT 令牌有效开始时间,辅助字段 |
exp | openid | JWT 令牌过期时间,辅助字段 |
aud | openid | 即应用的 ClientID,辅助字段 |
at_hash | openid | AccessToken 哈希值,辅助字段 |
phone_number | phone | 电话号码,比如 |
phone_number_verified | phone | 电话号码是否被验证过,目前默认电话号码是已验证 |
电子邮箱,比如 | ||
email_verified | 电子邮箱是否被验证过,目前默认电子邮箱是已验证 | |
name | profile | 用户显示名 |
preferred_username | profile | 用户的 |
updated_at | profile | 用户资料最后更新时间 |
3.3. 令牌端点支持的认证方式
根据 OIDC 协议指明,IDaaS 提供灵活性,允许以下 4 种不同方式进行身份验证。
在发现端点中返回的字段 token_endpoint_auth_methods_supported
指定了支持的认证方法。
取值 | 说明 |
none | 用于 Public 客户端,通过 |
client_secret_basic | 按规范 RFC 6749 - The OAuth 2.0 Authorization Framework 实现 |
client_secret_post | 按规范 RFC 6749 - The OAuth 2.0 Authorization Framework 实现 |
client_secret_jwt | 按规范 OpenID Connect Core 1.0 实现 |
上一步接收到 code 授权码,并验证请求合法(验证 state 与发起请求传入的一致)后,应用的后端服务,应使用获得的 code 向令牌端点(Token Endpoint)发起 POST 请求,请求示例如下:
以 client_secret_basic
为例,令牌端点请求样例为:
POST /token HTTP/1.0
Host: api.aliyunidaas.com
Authorization: Basic YXBwX21pY2hzN3I0*******cHllOkNTKioqKioq
grant_type=authorization_code&
code=COE59pkCTm4J*******arGEaiShj7NCzLCYu9
更多说明参看 OIDC Core 1.0 规范。
3.4. 应用 Client Secret 轮转
请参考 基本配置 中密钥轮转章节说明。
相关标准
RFC6749 - The OAuth 2.0 Authorization Framework
RFC6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
RFC7009 - OAuth 2.0 Token Revocation
RFC7515 - JSON Web Signature (JWS)
RFC7517 - JSON Web Key (JWK)
RFC7518 - JSON Web Algorithms (JWA)
RFC7519 - JSON Web Token (JWT)
RFC7636 - Proof Key for Code Exchange by OAuth Public Clients
RFC8252 - OAuth 2.0 for Native Apps
RFC8628 - OAuth 2.0 Device Authorization Grant
OpenID Connect Core 1.0
OpenID Connect Discovery 1.0