前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何生成腾讯会议SDK鉴权所需的SDK Token和ID Token等信息

如何生成腾讯会议SDK鉴权所需的SDK Token和ID Token等信息

原创
作者头像
liquid
修改2024-07-01 20:02:03
3.2K6
修改2024-07-01 20:02:03
举报
文章被收录于专栏:腾讯会议

什么是SDK Token?

客户(机构)的安全凭证,用来验证用户所属企业是否有效。

什么是ID Token?

对企业下的用户账号进行鉴权的安全凭证,用来验证用户账号是否有效。

如何获取参数&各参数介绍

在开通SDK配置之后,会从腾讯侧获取到SDK接入所需的对接参数。与Token生成相关的具体参数及用途如下:

1、SDK ID:企业SDK应用的唯一标识,在生成SDK Token和SDK初始化时使用。

2、SDK Secret:SDK秘钥,和SDK ID一起,用于生成SDK Token。

3、SSO_URL前缀:用于和ID Token一起拼接成SSO_URL,在SDK登录时使用。

4、公私钥文件:用于生成ID Token。

SDK的初始化流程

关键流程说明

步骤1:客户的Client端通过自有的协议向客户自己的Server端请求获取sdk_id和sdk_token数据,该流程属于客户自身业务实现,与腾讯会议无关。

步骤2&3:客户Server端返回sdk_id和sdk_token,这里生成的逻辑将在下文介绍。

步骤4:调用SDK的初始化接口,并返回结果。需要注意的是,这里SDK Token并没有被送到腾讯会议后台进行验证,只是在本地做了一个检查,后面调用登录接口的时候才会送到后台进行验证,也就是说如果SDK Token是无效的,不一定会在初始化的时候就报错

SDK的登录流程

关键流程说明

步骤1&2&3:客户的Client和Server端的逻辑,与会议SDK无关。

步骤4&5:生成ID Token并将腾讯侧提供的SSO_URL前缀参数和ID Token拼接成SSO_URL并返回,具体方法见下文。

步骤6:使用从客户Server端获取的SSO_URL调用SDK的登录接口。

步骤7&8:SDK向IDaaS和腾讯会议Server端发送SSO_URL和SDK Token,后台返回鉴权结果。

步骤9:SDK通过OnLogin回调返回登录结果给客户的Client。

SDK Token字段介绍

SDK Token是JWT的格式,其各个部分定义如下:

Header

代码语言:javascript
复制
{
  "alg": "HS256",
  "typ": "JWT"
}

以上部分为固定值,按照上面写死即可。签名算法是SHA256。

Payload

代码语言:javascript
复制
{
  "aud": "Tencent Meeting",	// 受众,固定写死为"Tencent Meeting"
  "exp": 1590804000,		// Token过期时间,由客户自己决定(单位:秒)
  "iat": 1588212000, 		// Token签发生成时间(当前时间,单位:秒)
  "iss": "2012081666" 		// 申请到的SDK ID
}

数据Payload部分包含以上四个属性,按要求填写即可。 所有涉及到时间的属性,都是Unix时间戳,即从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

Signature

客户的Server端将SDK Secret作为签名的secret,对整个数据进行SHA256签名。

ID Token字段介绍

ID Token是JWT的格式,其各部分定义如下:

Header

代码语言:javascript
复制
{
  "typ": "JWT",
  "alg": "RS256"
}

以上字段都是固定值,其中alg字段是RS256,表示用RSA签名。

Payload

代码语言:javascript
复制
{
  "sub": "123456789",		//IDaaS系统中的username字段,对应腾讯会议的userId字段
  "iss": "2012081666",		//SDK ID
  "name": "tencent_dev04",	//IDaaS系统中的displayName字段,对应腾讯会议的username字段,即显示名称
  "exp": 1619554966,		//Token过期时间(单位:秒)
  "iat": 1601387166 		//Token签发生成时间(当前时间,单位:秒)
}

数据Payload部分包含以上五个属性,按要求填写即可。 所有涉及到时间的属性,都是Unix时间戳,即从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

Signature

客户与IDaaS服务方约定生成的ID Secret,此处客户的Server端使用该ID Secret对整个数据进行签名,RSA签名方式。

如何实现

根据前面的信息,总结出以下要点需要在后续实现中考虑(部分和SDK端侧接入相关,不在本文实现范围内,这里先提出来):

1、Token生成的代码和密钥要部署在Server端,不可在终端程序上实现。

2、客户Client端在初始化时要向客户Server端获取SDK ID和SDK Token参数。

3、客户Client端在登录时要向客户Server端获取SSO_URL参数。

4、SDK在初始化时只对SDK Token做了简单校验,在调用登录接口时才传到后台进行验证。

5、SDK有本地登录缓存,有效期就是初始化传入的SDK Token的有效期,因此SDK Token的有效期一般要设置的长一点,至少要比客户Client的登录有效期长。过期的条件是时间超过SDK Token设置的有效期或者调用logout接口登出账号。

6、SDK的本地登录缓存用于快速登录,初始化时或者运行过程中调用refreshSDKToken接口会刷新缓存的有效期,使其与新的SDK Token有效期保存一致。

7、ID Token只在登录验证时有效即可,因此有效期可以设置的比较短,一般是5分钟。

8、SSO_URL是由开通SDK时获取的SSO_URL前缀参数和ID Token拼接而成。

本文实现生成SDK Token和ID Token,并且封装后提供给SDK初始化和登录时使用。实现分为以下几个模块:

1、PemUtils:秘钥文件处理工具类。

2、JWTConfig:参数配置。

3、JWTToken:生成SDK Token,返回SDK ID,生成拼接后的SSO_URL。

如何验证生成的token是否正确

当使用生成的SDK Token和ID Token进行初始化登录时报错,需要快速排查Token是否有效,可以用下面的方法。

验证SDK Token的有效性

1、在https://jwt.io/ 网页左边框输入生成的SDK Token,右边输入SDK secret参数,不勾选base64 encode。

2、左下角显示Signature Verified并且Header和Payload参数字段都正确,说明Token是有效的。

验证ID Token的有效性

1、在https://jwt.io/ 网页左边框输入生成的ID Token。

2、检查Header和Payload参数字段是否正确。

3、将拼接后的SSO_URL贴到浏览器地址栏打开,如果能调起腾讯会议客户端并登录成功,说明ID Token有效。

当ID Token无效时,在浏览器的地址栏会有报错提示。例如下图是因为ID Token已过期。

具体代码实现

秘钥文件处理

代码语言:javascript
复制
public class PemUtils {

    private static byte[] parsePEMFile(File pemFile) throws IOException {
        if (!pemFile.isFile() || !pemFile.exists()) {
            throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
        }
        PemReader reader = new PemReader(new FileReader(pemFile));
        PemObject pemObject = reader.readPemObject();
        byte[] content = pemObject.getContent();
        reader.close();
        return content;
    }

    private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {
        PublicKey publicKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm);
            EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            publicKey = kf.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the public key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the public key");
        }

        return publicKey;
    }

    private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) {
        PrivateKey privateKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm);
            EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            privateKey = kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the private key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the private key");
        }

        return privateKey;
    }

    public static PublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException {
        byte[] bytes = PemUtils.parsePEMFile(new File(filepath));
        return PemUtils.getPublicKey(bytes, algorithm);
    }

    public static PrivateKey readPrivateKeyFromFile(String filepath, String algorithm) throws IOException {
        byte[] bytes = PemUtils.parsePEMFile(new File(filepath));
        return PemUtils.getPrivateKey(bytes, algorithm);
    }

}

全局参数配置

代码语言:javascript
复制
public class JWTConfig {
    public static final String PUBLIC_KEY_FILE_RSA = "src/test/resources/rsa_public_key.pem";
    public static final String PRIVATE_KEY_FILE_RSA = "src/test/resources/rsa_private_key.pem";
    public static final String HS256_Secret = "";
    public static final String SDK_ID = "";
    // 生成SDK token的有效期时长,不短于客户端登录态的有效期,单位毫秒
    public static final long SDK_TOKEN_PERIOD_OF_VALIDITY = 30*24*60*60*(1000L);
    // 生成ID token的有效期时长,5分钟即可,单位毫秒
    public static final long ID_TOKEN_PERIOD_OF_VALIDITY = 5*60*1000;
    public static final String SSO_URL = "https://test-idp.id.meeting.qq.com/cidp/custom/ai-2bd60857a7fd42a9b0887e321f16776a/ai-37586921eda647e580c409bef20a1b82?id_token=";
}

Token生成与封装

代码语言:javascript
复制
public class JWTToken {
    private static String SDKTokenEncode(String secret, Map<String, Object> headerClaims, Map<String, Object> payloadClaims) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            String token = JWT.create()
                    .withHeader(headerClaims)
                    .withAudience((String) payloadClaims.get("aud"))
                    .withExpiresAt((Date) payloadClaims.get("exp"))
                    .withIssuedAt((Date) payloadClaims.get("iat"))
                    .withIssuer((String) payloadClaims.get("iss"))
                    .sign(algorithm);

            return token;
        } catch (JWTCreationException exception){
            //Invalid Signing configuration / Couldn't convert Claims.
        }
        return null;

    }

    public static String generateSDKToken() {
        //HMAC
        String sdkSecret = JWTConfig.HS256_Secret;
        Map<String, Object> sdkTokenHeaderClaims = new HashMap<String, Object>();
        Map<String, Object> sdkTokenPayloadClaims = new HashMap<String, Object>();
        //JWT Header
        sdkTokenHeaderClaims.put("alg", "HS256");
        sdkTokenHeaderClaims.put("typ", "JWT");
        //JWT Payload
        sdkTokenPayloadClaims.put("aud", "Tencent Meeting");
        sdkTokenPayloadClaims.put("iss", JWTConfig.SDK_ID);
        long iatSDKTokenTimeMillis = System.currentTimeMillis();
        long expSDKTokenTimeMillis = iatSDKTokenTimeMillis + JWTConfig.SDK_TOKEN_PERIOD_OF_VALIDITY;
        sdkTokenPayloadClaims.put("iat", new Date(iatSDKTokenTimeMillis));
        sdkTokenPayloadClaims.put("exp", new Date(expSDKTokenTimeMillis));

        String sdkToken = SDKTokenEncode(sdkSecret, sdkTokenHeaderClaims, sdkTokenPayloadClaims);

        return sdkToken;
    }

    private static String IDTokenEncode(RSAPublicKey publicKey, RSAPrivateKey privateKey, Map<String, Object> headerClaims, Map<String, Object> payloadClaims) {
        try {
            Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
            String token = JWT.create()
                    .withHeader(headerClaims)
                    .withSubject((String) payloadClaims.get("sub"))
                    .withIssuer((String) payloadClaims.get("iss"))
                    .withClaim("name", (String) payloadClaims.get("name"))
                    .withExpiresAt((Date) payloadClaims.get("exp"))
                    .withIssuedAt((Date) payloadClaims.get("iat"))
                    .sign(algorithm);
            return token;
        } catch (JWTCreationException exception){
            //Invalid Signing configuration / Couldn't convert Claims.
        }
        return null;

    }

    private static String generateIDToken(String userid, String username) throws IOException {
        //RSA
        RSAPublicKey publicKey = (RSAPublicKey) PemUtils.readPublicKeyFromFile(JWTConfig.PUBLIC_KEY_FILE_RSA, "RSA");//Get the key instance
        RSAPrivateKey privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(JWTConfig.PRIVATE_KEY_FILE_RSA, "RSA");//Get the key instance
        Map<String, Object> idTokenHeaderClaims = new HashMap<String, Object>();
        Map<String, Object> idTokenPayloadClaims = new HashMap<String, Object>();
        //JWT Header
        idTokenHeaderClaims.put("alg", "RS256");
        idTokenHeaderClaims.put("typ", "JWT");
        //JWT Payload
        idTokenPayloadClaims.put("iss", JWTConfig.SDK_ID);
        idTokenPayloadClaims.put("sub", userid);
        idTokenPayloadClaims.put("name", username);
        long iatIdTokenTimeMillis = System.currentTimeMillis();
        long expIdTokenTimeMillis = iatIdTokenTimeMillis + JWTConfig.ID_TOKEN_PERIOD_OF_VALIDITY;
        idTokenPayloadClaims.put("iat", new Date(iatIdTokenTimeMillis));
        idTokenPayloadClaims.put("exp", new Date(expIdTokenTimeMillis));

        String idToken = IDTokenEncode(publicKey, privateKey, idTokenHeaderClaims, idTokenPayloadClaims);

        return idToken;
    }

    public static String getSdkId() {
        return JWTConfig.SDK_ID;
    }

    public static String getSsoUrl(String userid, String username) {
        String idToken;
        try {
            idToken = generateIDToken(userid, username);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return JWTConfig.SSO_URL+idToken;
    }
}

接口调用测试

代码语言:javascript
复制
public class main {
    public static void main(String[] args) throws IOException {

        String userid = "test";
        String username = "test";

        //SDK初始化参数获取
        String sdkId = JWTToken.getSdkId();
        String sdkToken = JWTToken.generateSDKToken();
        System.out.println("SDK ID: " + sdkId);
        System.out.println("SDK Token: " + sdkToken);

        //SDK登录参数获取
        String ssoUrl = JWTToken.getSsoUrl(userid, username);
        System.out.println("username: " + userid);
        System.out.println("SSO URL: " + ssoUrl);

    }
}

参考文档

SDK鉴权与登录说明

本文源码下载

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是SDK Token?
  • 什么是ID Token?
  • 如何获取参数&各参数介绍
  • SDK的初始化流程
    • 关键流程说明
    • SDK的登录流程
      • 关键流程说明
      • SDK Token字段介绍
      • ID Token字段介绍
      • 如何实现
      • 如何验证生成的token是否正确
        • 验证SDK Token的有效性
          • 验证ID Token的有效性
          • 具体代码实现
            • 秘钥文件处理
              • 全局参数配置
                • Token生成与封装
                  • 接口调用测试
                  • 参考文档
                  • 本文源码下载
                  相关产品与服务
                  腾讯会议
                  腾讯会议(Tencent Meeting)为企业打造专属的会议能力,卓越的音视频性能,丰富的会议协作能力,坚实的会议安全保障,提升协作效率,满足大中小会议全场景需求。您可以使用腾讯会议进行远程音视频会议、在线协作、会管会控、会议录制、指定邀请、布局管理、同声传译等。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档