客户身份(CIAM)

ID Token单点登录方案

# 文档说明

本文用于指导通过OIDC认证源发起认证的方式,实现免密登录到已与IDaaS集成的各应用系统。 OIDC认证源发起认证是指:应用端作为OIDC OP(OpenID Provider),IDaaS平台作为OIDC RP(Relying Party),应用端生成用户的ID Token信息,并传递给平台进行验签和匹配当前用户身份信息。若验签并匹配用户成功,单点登录进入到已与IDaaS平台集成的目标应用系统。

# 集成工作

# 集成说明

  1. 应用开发者生成公私钥证书,采用JWT格式封装用户的信息,使用证书私钥进行签名生成ID Token,然后调用平台的认证接口并传递给平台ID Token进行认证

  2. 平台管理端配置相关的OIDC认证源参数,如ID Token的证书公钥等相关信息,当平台接收到ID Token后,根据配置信息进行Token验签,解析出用户的信息并进行用户匹配。验签通过并匹配用户后,强制浏览器重定向到应用传递给平台的访问目标应用url。因目标应用系统已与IDaaS完成认证对接,此时用户可实现单点登录进入目标应用系统。

# 单点登录流程

从APP单点登录到与IDaaS集成的H5应用

# 应用开发者生成ID Token

  1. 应用方生成密钥对,提供公钥信息。

  2. 应用方生成用户id_token,获取需要跳转的应用地址。主动向IDaaS发起认证请求,具体参考“OIDC认证源发起认证接口”及“参考示例”。

​ 注:id_token的主要参数请参考以下示例:

  • HEADER部分
  1. 参数示例
{ "kid": "14a0b7d31d5d284c549f9e3565fb136a", "alg": "RS256" }
1
  1. 参数说明:
参数名 是否必填 描述
kid 验证身份令牌签名时使用的秘钥id
alg 签名算法
  • PAYLOAD部分
  1. 参数示例
{ "iss": "https://xxx.com ", "aud": "https://{your_domain}", "exp": 1655779413, "jti": "B6P99VAWZQZBGNa4avp29s", "iat": 1655779293, "nbf": 1655779173, "sub": "subject" }
1
  1. 参数说明:
参数名 是否必填 描述
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回调地址完成认证。

  1. 请求说明

GET https://{your_domain}/api/v1/oidc/sso/{idpId}

  1. 请求Header参数

  1. 请求包体示例

  1. 请求参数
参数名 中文名称 必须 类型 示例
id_token ID Token 必须 String
redirect_to 重定向地址 可选 String 需要访问的目标应用系统的访问地址,参数为空时默认跳转IDaaS用户中心
  1. 请求示例
https://{your_domain}/api/v1/oidc/sso/202208231445-0FE9-93C4AFCDA?id_token=eyJhbGciOiJJ9.eyJzdWIiOiJ6aG91eGNoIiwiaWQiOiIyMDIyMTIyMDE1zIjdlIZmFiMDVkNTg3YyJ9.bfCdG5PcttXzoEA8kuOf81SrQ&redirect_to=https://demo.com
1
  1. 返回示例
HTTP Status: 302 REDIRECT [https://demo.com]
1
  1. 返回参数

# 参考示例

# 生成公私钥

1.在线生成pem格式的公私钥,可以参考:http://www.metools.info/code/c80.html

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>

1
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;
    }

1
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>
1
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;
    }
1
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