# 使用 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 鉴权模式,您需要在 智能硬件控制台 中的 域配置 页面进行鉴权配置。
点此直接访问域配置页面 (opens new window);
或者手动访问该界面:点击右上角 用户名 -> 下拉列表中选择 基本信息 -> 左侧 域配置 。
选择 Token 鉴权模式 。(需要有 Token 鉴权模式的权限才会显示)
选择 开启过渡 或 不过渡
- 开启过渡 模式:若登录时的密码验证 Token 失败,依然会成功登录。若在一个域中同时包含多种类型的应用,如:同时包含 WebRTC 和其他类型的应用,或者同时包含 IoT 和其他类型的应用,则 必须开启过渡模式。否则 IoT 和 WebRTC 的 App 将无法登录。
- 不过渡 模式:若登录时的密码验证 Token 失败,会登录失败并提示具体错误原因。
点击保存。
# 获取 AppSecret
AppSecret 是生成生成 Token 的必要参数。在生成 Token 前,您需要在控制台获取必要的参数AppSecret
:
进入控制台 - 服务管理 - 应用管理 。
在 操作 一栏中点击 设置 图标。
在 状态配置 中 点击 AppSecret 后的 添加 按钮。
TIP
每个应⽤最多⽣成两个 AppSecret。
# 生成 Token
生成 Token 分为生成临时 Token 和生成正式 Token 两种。在项目集成测试阶段,可使用由菊风平台提供的临时 Token 进行测试,有效期为 24 小时。在项目准备正式上线阶段,开发者需要在自己的服务端部署 Token 服务来生成正式 Token。
# 生成临时 Token
为了方便测试,菊风提供生成临时 Token 的功能。您可以在菊风云控制台获取临时 Token ,具体步骤如下:
进入控制台 - 服务管理 - 应用管理 。
在 操作 一栏中点击 设置 图标。
在 状态配置 一栏中 点击 临时 Token 后的 添加 按钮。
输入AccountID,然后点击生成临时 Token。
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 的有效性:
- 进入控制台 - 服务管理 - 应用管理 。
- 在 操作 一栏中点击 设置 图标。
- 在 安全信息 一栏中点击 验证Token 后的图标。
# 集成 Token
在验证 Token 后,需要在集成 发起登录 功能时将 Token 作为password
参数传入 login (opens new window) 。以 Java 代码为例:
JCClient.LoginParam loginParam = new JCClient.LoginParam();
// 发起登录
mClient.login("userID", "Token", loginParam);
更多登录相关的集成文档请访问:一对一语音通话(或其他产品) -> 基本功能集成 -> 登录 。