# 使用 Token

Token 是一种动态密钥,通过 AppKey、AppSecret、用户名、有效时间戳等参数生成,安全性较高。在正式生产环境等对安全要求较高的场景中,我们推荐使用 Token 鉴权。

# 前提条件

在生成 Token 前,需要在控制台开启 Token 鉴权模式,并在控制台中获取到 AppSecret 。

# 开启 Token 鉴权模式

开启 Token 鉴权模式是生成 Token 的必要条件。只有在开启 Token 鉴权模式后您才可以在控制台获取 AppSecret,AppSecret 将用于生成 Token 。

TIP

  • IoT 设备暂时不支持 Token 鉴权。

  • WebRTC 暂时不支持 Token 鉴权。

  • 开启 Token 鉴权模式会影响域下的所有应用。

  • 若在一个域中同时包含多种类型的应用,如:同时包含 WebRTC 和其他类型的应用,或者同时包含 IoT 和其他类型的应用,则 必须开启过渡模式 。否则 IoT 和 WebRTC 的 App 将无法登录。

如果您想要开启 Token 鉴权模式,您需要在 智能硬件控制台 中的 域配置 页面进行鉴权配置。

  1. 点此直接访问域配置页面 (opens new window)

    或者手动访问该界面:点击右上角 用户名 -> 下拉列表中选择 基本信息 -> 左侧 域配置

  2. 选择 Token 鉴权模式 (需要有 Token 鉴权模式的权限才会显示)

  3. 选择 开启过渡不过渡

    • 开启过渡 模式:若登录时的密码验证 Token 失败,依然会成功登录。若在一个域中同时包含多种类型的应用,如:同时包含 WebRTC 和其他类型的应用,或者同时包含 IoT 和其他类型的应用,则 必须开启过渡模式。否则 IoT 和 WebRTC 的 App 将无法登录。
    • 不过渡 模式:若登录时的密码验证 Token 失败,会登录失败并提示具体错误原因。
  4. 点击保存

image-20201207115654730

# 获取 AppSecret

AppSecret 是生成生成 Token 的必要参数。在生成 Token 前,您需要在控制台获取必要的参数AppSecret

  1. 进入控制台 - 服务管理 - 应用管理

  2. 操作 一栏中点击 设置 image-20200925141010930 图标。

  3. 状态配置 中 点击 AppSecret 后的 添加 image-20200925140937588 按钮。

TIP

每个应⽤最多⽣成两个 AppSecret。

image-20201207161447841

# 生成 Token

生成 Token 分为生成临时 Token 和生成正式 Token 两种。在项目集成测试阶段,可使用由菊风平台提供的临时 Token 进行测试,有效期为 24 小时。在项目准备正式上线阶段,开发者需要在自己的服务端部署 Token 服务来生成正式 Token。

# 生成临时 Token

为了方便测试,菊风提供生成临时 Token 的功能。您可以在菊风云控制台获取临时 Token ,具体步骤如下:

  1. 进入控制台 - 服务管理 - 应用管理

  2. 操作 一栏中点击 设置 image-20200925141010930 图标。

  3. 状态配置 一栏中 点击 临时 Token 后的 添加 image-20200925140937588 按钮。

  4. 输入AccountID,然后点击生成临时 Token

img

WARNING

  • 临时 Token 仅作为演示和测试用途。在生产环境中,需要自行部署服务器生成 Token 。

  • 临时 Token 的有效期为 24 小时,用于在项目测试阶段进行鉴权。

# 生成正式 Token

正式 Token 将用于正式上线阶段,生成参数包括 AppKey,AppSecret,用户名,有效期等。在项目准备正式上线阶段,开发者需要在自己的服务端部署 Token 服务,并自行生成 Token。

在生成 Token 之前,需要准备以下参数:

参数 描述 备注
AppKey AppKey 是应用在 菊风云平台 中的唯一标识,类似应用的身份证。通过在平台创建应用获取。 通过创建应用获取
AppSecret AppSecret 为长度 20 的随机字符串,数字和小写字母生成的 AppSecret 保存在对应数据库的 App 属性中 菊风提供, 点击查看如何获取 AppSecret
AccountID 用户名,在 SDK 集成中使用的 userID 自定义
Salt 随机生成的数,范围 1-254 自定义
Timestamp 过期时间的UNIX时间戳,默认是当前时间 + 24小时 自定义

# 示例代码

生成 Token 的伪代码如下:

Ver             = '01'
AppKey          = <从 Portal 获取的字符串>
AppSecret       = <从 Portal 获取的字符串>
AccountID       = <App 请求携带的字符串,长度 1-128>
Salt            = <随机生成的数,范围 1-254>
Timestamp       = <过期时间的UNIX时间戳,默认是当前时间 + 24小时>

base64          = <base64 编码函数>
byte1           = <编码1字节整数的函数,编码数值需要与Salt进行异或运算>
byte4           = <使用大端字节序编码4字节整数的函数,编码数值每个字节需要与Salt进行异或运算>
strx            = <编码字符串的函数,先使用byte1函数编码字符串长度,然后编码与Salt异或之后的字符串字节序列>
hmac-sha256     = <使用SHA256算法的HMAC散列算法>
substr          = <获取字符串子串的函数,指定起始偏移和长度>

Payload         = base64( byte1(Salt) + byte4(xor(Timestamp, Salt)) + byte4(xor(crc32(AppKey), Salt)) + strx(AccountID, Salt) )
Signature       = base64( hmac-sha256(AppSecret, Payload) )

AppSecretDigest = substr(AppSecret, 0, 6)

Token           = Ver + AppSecretDigest + Signature + Payload
PHP 生成 Token:
<?php
  class CreateToken{
  private function byte1w($val,$salt = 0){
    $ds = pack('C', intval($val) ^ $salt);
    return $ds;
  }

  private function byte4w($val, $salt = 0){
    $salt32 = ($salt << 24) + ($salt << 16) + ($salt << 8) + $salt;
    $dfrw = pack('N', intval($val ^ $salt32));
    return $dfrw;
  }

  private function xstrw($val, $salt){
    $ret = $this->byte1w(strlen($val), $salt);
    $strlen = strlen($val);
    for ($i=0; $i < $strlen ; $i++) {
      $ret .= $this->byte1w(($salt ^ ord($val[$i])) & 0xff);
    }
    return $ret;
  }
  /**
     * @param $AppKey
     * @param $AppSecret  密钥
     * @param $AccountId  
     * @param ... 
     * @return token
     */
  public function edit_build($Ver,$AppKey,$AppSecret,$AccountId,$Salt,$Timestamp){
    //加密过程
    $first = $this->byte1w($Salt);
    $second = $this->byte4w($Timestamp, $Salt);
    $third = $this->byte4w(crc32($AppKey) & 0xffffffff, $Salt);
    $fourth = $this->xstrw($AccountId, $Salt);
    //组合生成
    $Payload = base64_encode($first . $second . $third . $fourth);
    $AppSecretDigest = substr($AppSecret, 0, 6);
    $digest = hash_hmac("sha256",$Payload, $AppSecret,true);
    $Signature = base64_encode($digest);

    return $Ver . $AppSecretDigest . $Signature . $Payload;
  }
}
$Salt = mt_rand(1,254); //盐1~254随机整数
$Timestamp = time() + 24 * 3600;//过期时间 默认当前时间戳+有效期 当前是1天 单位到秒 
$ver = '01'; //版本号默认
$AppKey = '';//Appkey 在控制台服务管理->应用管理->安全信息处可见
$AppSecret = '';//应用密钥
$AccountId = 'test1'; //登录用户名

$PCreateToken = new CreateToken;
$Token = $PCreateToken->edit_build($ver,$Appkey,$AppSecret,$AccountId,$Salt,$Timestamp);

echo $Token;
?>
python 生成 Token:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import time
import hmac
import base64
import random
import struct
from zlib import crc32
from hashlib import sha256


def byte1w(val, salt = 0):
    return struct.pack('>B', int(val) ^ salt)


def byte1r(val, salt = 0):
    ret, = struct.unpack('>B', val)
    return ret ^ salt


def byte2w(val, salt = 0):
    salt16 = (salt << 8) + salt
    return struct.pack('>H', int(val) ^ salt16)


def byte2r(val, salt = 0):
    ret, = struct.unpack('>H', val)
    return ret ^ ((salt << 8) + salt)


def byte4w(val, salt = 0):
    salt32 = (salt << 24) + (salt << 16) + (salt << 8) + salt
    return struct.pack('>I', int(val ^ salt32))


def byte4r(val, salt = 0):
    ret, = struct.unpack('>I', val)
    return ret ^ ((salt << 24) + (salt << 16) + (salt << 8) + salt)


def xstrw(val, salt = 0):
    ret = byte1w(len(val), salt)
    for i in range(len(val)):
        ret += byte1w((salt ^ ord(val[i])) & 0xff)
    return ret


def xstrr(val, salt):
    cnt = byte1r(val[0:1], salt)
    ret = ''
    for i in range(cnt):
        ret += chr((salt ^ byte1r(val[i+1:i+2])) & 0xff)
    return ret


class AuthToken:
    def __init__(self, AppKey="", AppSecret="", AccountId=""):
        random.seed(time.time())
        self.Ver = '01'
        self.AppKey = AppKey
        self.AppSecret = AppSecret
        self.AccountId = AccountId
        self.Salt = random.randint(1, 254)
        self.Timestamp = int(time.time()) + 24 * 3600
        self.Error = ""

    
    def build(self):
        if len(self.AppSecret) <= 6:
            raise ValueError('Invalid AppSecret')
        
        if len(self.AccountId) == 0:
            raise ValueError('No AccountID')
        
        if len(self.AccountId) > 128:
            raise ValueError('AccountID Too Long')

        Payload = base64.b64encode(byte1w(self.Salt) + byte4w(self.Timestamp, self.Salt) + byte4w(crc32(self.AppKey) & 0xffffffff, self.Salt) + xstrw(self.AccountId, self.Salt))
        AppSecretDigest = self.AppSecret[0:6]
        Signature = base64.b64encode(hmac.new(self.AppSecret, Payload, sha256).digest())

        return self.Ver + AppSecretDigest + Signature + Payload

def main():
    at = AuthToken(AppKey="******************", AppSecret="******************", AccountId="******************")
    token = at.build()
    print(token)

if __name__ == "__main__":
    main()

python3 生成 Token:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import time
import hmac
import base64
import random
import struct
from binascii import crc32
from hashlib import sha256


def byte1w(val, salt=0):
    return struct.pack('>B', int(val) ^ salt)


def byte1r(val, salt=0):
    ret, = struct.unpack('>B', val)
    return ret ^ salt


def byte2w(val, salt=0):
    salt16 = (salt << 8) + salt
    return struct.pack('>H', int(val) ^ salt16)


def byte2r(val, salt=0):
    ret, = struct.unpack('>H', val)
    return ret ^ ((salt << 8) + salt)


def byte4w(val, salt=0):
    salt32 = (salt << 24) + (salt << 16) + (salt << 8) + salt
    return struct.pack('>I', int(val ^ salt32))


def byte4r(val, salt=0):
    ret, = struct.unpack('>I', val)
    return ret ^ ((salt << 24) + (salt << 16) + (salt << 8) + salt)


def xstrw(val, salt=0):
    ret = byte1w(len(val), salt)
    for i in range(len(val)):
        ret += byte1w((salt ^ ord(val[i])) & 0xff)
    return ret


def xstrr(val, salt):
    cnt = byte1r(val[0:1], salt)
    ret = ''
    for i in range(cnt):
        ret += chr((salt ^ byte1r(val[i + 1:i + 2])) & 0xff)
    return ret


class AuthToken:
    def __init__(self, AppKey="", AppSecret="", AccountId=""):
        random.seed(time.time())
        self.Ver = '01'
        self.AppKey = AppKey
        self.AppSecret = AppSecret
        self.AccountId = AccountId
        self.Salt = random.randint(1, 254)
        self.Timestamp = int(time.time()) + 24 * 3600
        self.Error = ""

    def build(self):
        if len(self.AppSecret) <= 6:
            raise ValueError('Invalid AppSecret')

        if len(self.AccountId) == 0:
            raise ValueError('No AccountID')

        if len(self.AccountId) > 128:
            raise ValueError('AccountID Too Long')

        Payload = base64.b64encode(byte1w(self.Salt) + byte4w(self.Timestamp, self.Salt) + byte4w(crc32(self.AppKey.encode()) & 0xffffffff,self.Salt) + xstrw(self.AccountId,self.Salt))
        AppSecretDigest = self.AppSecret[0:6]
        Signature = base64.b64encode(hmac.new(bytes(self.AppSecret.encode('utf-8')), Payload, sha256).digest())
        Signature_str = bytes.decode(Signature)
        Payload_str = bytes.decode(Payload)

        return self.Ver + AppSecretDigest + Signature_str + Payload_str


def main():
    at = AuthToken(AppKey="*************", AppSecret ="*************", AccountId ="*************")
    token = at.build()
    print(token)


if __name__ == "__main__":
    main()
Java 生成 Token
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.zip.CRC32;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.util.Random;


public class CreateToken {

    public static void main(String []args) {
        String ver = "01"; //版本号默认
        String appkey = ""; //Appkey 在控制台服务管理->应用管理->安全信息处可见
        String appSecret = "1233456789"; //应用密钥
        String accountId = "test1"; //登录用户名

        Random random = new Random();
        byte salt = (byte) (random.nextInt(254) + 1); //盐1~254随机整数
        int timestamp = (int) (System.currentTimeMillis() / 1000) + 24 * 3600; //过期时间 默认当前时间戳+有效期 当前是1天 单位到秒 

        try {
            BASE64Encoder encoder = new BASE64Encoder();
            byte first = byte1(salt, (byte) 0);
            byte[] second = byte4(timestamp, salt);
            CRC32 crc32 = new CRC32();
            crc32.update(appkey.getBytes());
            byte[] third = byte4((int) crc32.getValue(), salt);
            byte[] forth = strx(accountId, salt);
            ByteBuffer byteBuffer = ByteBuffer.allocate(1 + second.length + third.length + forth.length);
            byteBuffer.put(first).put(second).put(third).put(forth);
            String payload = encoder.encode(byteBuffer.array());
            String signature = generateSignature(appSecret, payload);
            String appSecretDigest = appSecret.substring(0, 6);
            String token = ver + appSecretDigest + signature + payload;
            System.out.println(token);
	    } catch (Exception e) {
		    System.out.println("BASE64加解密异常");
		    e.printStackTrace();
	    }
    }

    private static String generateSignature(String secret, String content) {
        try {
            BASE64Encoder encoder = new BASE64Encoder();
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            return encoder.encode(sha256_HMAC.doFinal(content.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return "";
    }

    private static byte byte1(byte input, byte salt) {
        return (byte) (input ^ salt);
    }

    private static byte[] byte4(int input, byte salt) {
        byte[] ret = new byte[4];
        ret[0] = byte1((byte) (input >> 24), salt);
        ret[1] = byte1((byte) (input >> 16), salt);
        ret[2] = byte1((byte) (input >> 8), salt);
        ret[3] = byte1((byte) (input), salt);
        return ret;
    }

    private static byte[] strx(String input, byte salt) {
        byte[] ret = new byte[input.length() + 1];
        ret[0] = byte1((byte) input.length(), salt);
        for (int i = 0; i < input.length(); i++) {
            ret[i + 1] = byte1(input.getBytes()[i], salt);
        }
        return ret;
    }
}
NodeJS 生成 Token
var CryptoJS = require('crypto-js');
var Long = require('long');
var crc32 = require('crc-32');
var UINT32 = require('cuint').UINT32;
var jspack = require('bufferpack');



var ord = function(str) {
	var n = str.charCodeAt(0);
    return n;
}

function randomNum(minNum,maxNum){ 
    switch(arguments.length){ 
        case 1: 
            return parseInt(Math.random()*minNum+1,10); 
        break; 
        case 2: 
            return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10); 
        break; 
            default: 
                return 0; 
            break; 
    } 
} 

//###########################################################

	function byte1w(val, salt = 0){
		var num = val ^ salt;
		var ds = jspack.pack('B', [num]);	
		return ds;
	}
	
	function byte4w(val, salt = 0){
		var fd = (salt << 24);
		if(fd){
			fd = (salt << 24 >>> 0);
		}
		var salt32 = fd + (salt << 16) + (salt << 8) + salt;
		var numa = val ^ salt32;
		if(numa<0){
			var num = numa >>> 0;
		}else{
			var num = numa;
		}

    	var Abbuf = new Uint32Array(4);
    	Abbuf[0]= 0xFF;
    	Abbuf[1]= 0xFF;
    	Abbuf[2]= 0xFF;
    	Abbuf[3]= 0xFF;
    	var buf = Buffer.from(Abbuf);
    	buf.writeUInt32BE(num);
    	return buf;
	}

	
	function xstrw(val, salt){
		var passArr = val.split('');
		var ret = byte1w(passArr.length, salt);
		var ret_buf_len = ret.length;
		var ret_arr = new Array();
		ret_arr.push(ret);
		for (var i = 0; i < passArr.length; i++) {
		    retz = byte1w((salt ^ ord(passArr[i])) & 0xff);
		    ret_buf_len += retz.length;
		    ret_arr.push(retz);
		}
		var ret_all = Buffer.concat(ret_arr, ret_buf_len);
	    return ret_all;
	}
	
	function pack_64(fd,B4,sd64,dref){
		pack_buf_len = fd.length+B4.length+sd64.length+dref.length;
		var pack_arr = new Array();
		pack_arr.push(fd);
		pack_arr.push(B4);
		pack_arr.push(sd64);
		pack_arr.push(dref);
		var pack_all = Buffer.concat(pack_arr, pack_buf_len);
		return pack_all.toString('base64');
	}
	
	var Ver = '01';
	var AppKey = '*************';
	var AppSecret = '*************';
	var AccountId = '*************';
	var Salt = randomNum(1,254);//102;
	var Timestamp = Math.floor(new Date() / 1000) + (24 * 3600);//1607857680;

	//###################################################
	
	var fd = byte1w(Salt);
	
	var B4 = byte4w(Timestamp,Salt);
	
	var sddf = UINT32(crc32.str(AppKey)).and(UINT32(0xffffffff)).toNumber();
	var sd64 = byte4w(sddf,Salt);
	
	var dref = xstrw(AccountId, Salt);

	var Payload = pack_64(fd,B4,sd64,dref);

	//####################################################
	
	AppSecretDigest = AppSecret.substring(0,6);
	var sds = CryptoJS.HmacSHA256(Payload,AppSecret);
	var Signature = CryptoJS.enc.Base64.stringify(sds);
	var token = Ver + AppSecretDigest + Signature + Payload;
	console.log("token:",token);
	

# 验证 Token

在生成 Token 后,前往控制台验证该 Token 的有效性:

  1. 进入控制台 - 服务管理 - 应用管理
  2. 操作 一栏中点击 设置 image-20200925141010930 图标。
  3. 安全信息 一栏中点击 验证Token 后的图标。

image-20210106151748115

# 集成 Token

在验证 Token 后,需要在集成 发起登录 功能时将 Token 作为password 参数传入 login (opens new window) 。以 Java 代码为例:

JCClient.LoginParam loginParam = new JCClient.LoginParam();
// 发起登录
mClient.login("userID", "Token", loginParam);

更多登录相关的集成文档请访问:一对一语音通话(或其他产品) -> 基本功能集成 -> 登录