ID Token单点登录方案
# 文档说明
本文用于指导通过OIDC认证源发起认证的方式,实现免密登录到已与IDaaS集成的各应用系统。 OIDC认证源发起认证是指:应用端作为OIDC OP(OpenID Provider),IDaaS平台作为OIDC RP(Relying Party),应用端生成用户的ID Token信息,并传递给平台进行验签和匹配当前用户身份信息。若验签并匹配用户成功,单点登录进入到已与IDaaS平台集成的目标应用系统。
# 集成工作
# 集成说明
应用开发者生成公私钥证书,采用JWT格式封装用户的信息,使用证书私钥进行签名生成ID Token,然后调用平台的认证接口并传递给平台ID Token进行认证
平台管理端配置相关的OIDC认证源参数,如ID Token的证书公钥等相关信息,当平台接收到ID Token后,根据配置信息进行Token验签,解析出用户的信息并进行用户匹配。验签通过并匹配用户后,强制浏览器重定向到应用传递给平台的访问目标应用url。因目标应用系统已与IDaaS完成认证对接,此时用户可实现单点登录进入目标应用系统。
# 单点登录流程
从APP单点登录到与IDaaS集成的H5应用
# 应用开发者生成ID Token
应用方生成密钥对,提供公钥信息。
应用方生成用户id_token,获取需要跳转的应用地址。主动向IDaaS发起认证请求,具体参考“OIDC认证源发起认证接口”及“参考示例”。
注:id_token的主要参数请参考以下示例:
- HEADER部分
- 参数示例
{ "kid": "14a0b7d31d5d284c549f9e3565fb136a", "alg": "RS256" }
- 参数说明:
参数名 | 是否必填 | 描述 |
---|---|---|
kid | 是 | 验证身份令牌签名时使用的秘钥id |
alg | 是 | 签名算法 |
- PAYLOAD部分
- 参数示例
{ "iss": "https://xxx.com ", "aud": "https://{your_domain}", "exp": 1655779413, "jti": "B6P99VAWZQZBGNa4avp29s", "iat": 1655779293, "nbf": 1655779173, "sub": "subject" }
- 参数说明:
参数名 | 是否必填 | 描述 |
---|---|---|
iss | 是 | 令牌颁发者,提供认证信息者的唯一标识, URI格式,一般为应用的域名 |
aud | 是 | 令牌接收者, 与OIDC身份提供商中配置的Audience一致 |
exp | 是 | 令牌的过期时间,时间戳(毫秒) |
iat | 是 | 令牌的签发时间,时间戳(毫秒) |
sub | 是 | 令牌主体,用户唯一标识 |
jti | 否 | 令牌的id |
# 平台配置OIDC认证源
开发者登录IDaaS企业中心,登录成功后,选择“认证 – 认证源管理”的菜单选项
进入认证源管理页面,选择“联邦认证源 – OIDC”
进入OIDC认证源配置,点击“添加认证源”,新增OIDC认证源。
- 基本配置:
“图标”:认证源显示logo
“显示名称”:IDaaS页面上显示的认证源名称,用于区分不同认证源
“认证方式”:选择认证源发起认证
“公钥格式”:支持四种格式,基于JWKURL,PEM格式公钥,JSON格式公钥、证书格式公钥
“公钥”:不同公钥格式对应不同的内容
“签名算法”:支持RS256算法
“Audience”:用于校验id_token 中的aud 参数是否与配置一致
“Logout URL”:IDaaS用户中心访问登出时,浏览器隐式调用此URL,一般填写认证源端登出url,此参数非必填
“调用地址”: 用于接收认证源放返回id_token的回调地址
- 关联策略:
“关联属性”:id_token解析后存在的属性key,默认为sub
“关联用户属性”:IDaaS平台用户唯一属性
“未关联用户时”:通过关联用户属性未自动绑定用户时的操作策略;若选择“失败”,则用户不存在时登录失败;若选择“自动创建用户”,则用户不存在时自动创建用户。
“更新已存在属性”: 关联用户时,是否需要根据映射配置更新IDaaS用户属性
# 传递ID Token请求认证接口
基于OIDC认证源端发起认证 ,IDaaS提供OIDC回调地址完成认证。
- 请求说明
GET https://{your_domain}/api/v1/oidc/sso/{idpId}
- 请求Header参数
无
- 请求包体示例
无
- 请求参数
参数名 | 中文名称 | 必须 | 类型 | 示例 |
---|---|---|---|---|
id_token | ID Token | 必须 | String | |
redirect_to | 重定向地址 | 可选 | String | 需要访问的目标应用系统的访问地址,参数为空时默认跳转IDaaS用户中心 |
- 请求示例
https://{your_domain}/api/v1/oidc/sso/202208231445-0FE9-93C4AFCDA?id_token=eyJhbGciOiJJ9.eyJzdWIiOiJ6aG91eGNoIiwiaWQiOiIyMDIyMTIyMDE1zIjdlIZmFiMDVkNTg3YyJ9.bfCdG5PcttXzoEA8kuOf81SrQ&redirect_to=https://demo.com
- 返回示例
HTTP Status: 302 REDIRECT [https://demo.com]
- 返回参数
无
# 参考示例
# 生成公私钥
1.在线生成pem格式的公私钥,可以参考:https://apiked.com/rsa
2.以生成JSON格式公钥为例,生成一个RS256算法的密钥如下。
第三方依赖包:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.50</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.50</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.19</version> <!-- 使用最新版本 -->
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
示例代码如下:
/**
* 生成秘钥
*/
public static RSAKey generatorKeys() throws Exception {
String kid = UUID.randomUUID().toString().replaceAll("-", "");
RSAKey key = new RSAKeyGenerator(2048)
.keyUse(KeyUse.SIGNATURE)
.algorithm(new Algorithm("RS256"))
.keyID(kid)
.generate();
System.out.println("不对外提供json格式私钥为:"+key.toJSONString());
System.out.println("可对外提供json格式的公钥为:"+key.toPublicJWK().toJSONString());
return key;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 生成用户id_token
根据用户唯一标识,生成id_token示例如下:
第三方依赖包:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.50</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.50</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.19</version> <!-- 使用最新版本 -->
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
示例代码如下:
/**
* 生成用户id_token
*/
public static String buildIdToken() throws Exception{
/**
* 1. 根据秘钥生成签名工具
*/
//json格式,parse方法参数为上一步生成的不对外提供json格式私钥
RSAKey rsaKey = (RSAKey)JWK.parse("{\"p\":\"yuaog5...nNgWLVg\",\"dp\":\"nhr2nPFE...LMP28KylCs0GdE\",\"alg\":\"RS256\",\"dq\":\"xdW66Lr10...6HZXFk\",\"n\":\"oGVHTUb9amuG...J8SAfBV7c49W0lSw\"}");
RSASSASigner rsassaSigner = new RSASSASigner(rsaKey);
/**
* 2. 建立header头
*/
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaKey.getKeyID()).build();
/**
* 2. 建立payload载体
*/
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
// aud必填, IDaaS认证源中与 Audience 的配置保持一致
.audience("http://{your_domain}")
// iss必填, 必须是URI格式, 第三方应用域名
.issuer("http://xxx.com")
// sub必填, 根据平台配置,sub为用户的唯一标识
.subject("zhangsan")
// iat必填, token签发时间
.issueTime(new Date())
// exp token过期时间
.expirationTime(new Date(System.currentTimeMillis() + (1000 * 60 * 5)))
// 自定义属性,非必填
.claim("mobile", "18310773289")
.build();
/**
* 3. 建立签名
*/
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
signedJWT.sign(rsassaSigner);
/**
* 4. 生成id_token
*/
String id_token = signedJWT.serialize();
System.out.println("id_token为:"+ id_token);
return id_token;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43