小鱼签名鉴权方法2.0可以按需选择加密方式,更为安全,推荐使用该签名方法计算API请求签名。使用签名鉴权2.0,需要使用域名 https://sdkapi.xylink.com 进行API调用请求。
小鱼会对每个请求的API的企业身份、公共参数、签名进行验证,故每个API请求都需要包括企业身份(enterpriseId)、签名(x-xy-sign)、公共参数。
签名的计算需要enterpriseId和signSecret、x-xy-clientid。所以在使用api进行签名计算之前,您需要从云视讯管理平台获得enterpriseId、x-xy-clientid、x-xy-clientsecret,得到以上三个必要参数之后,方可对REST API进行签名计算。
REST URL
POST https://sdkapi.xylink.com/admin/login/oauth/app_token
参数说明:
参数 | 参数类型 | 必须 | 参数位置 | 默认值 | 说明 |
x-xy-clientid | String | 是 | Header | 无 | 小鱼向三方提供的应用id,可在小鱼云视讯管理平台获取 |
x-xy-clientsecret | String | 是 | Header | 无 | 小鱼向三方提供的应用密钥,可在小鱼云视讯管理平台获取 |
enterpriseId | String | 是 | Body | 无 | 企业ID,可在小鱼云视讯管理平台获取 |
请求体示例(Json)
{
"enterpriseId": "***"
}
请求成功结果示例:
{
"code": 0,
"message": "success",
"path": "",
"data": {
"access_token": "baeaccfd-f649-4ed6-85b6-cba7dfd54f60",
"token_type": "bearer",
"refresh_token": "1b9d021e-e067-44e3-b0b3-b1ce3e18cc66",
"expires_in": 37820,
"scope": "userProfile",
"signType": [
"HMAC_SHA256",
"SHA256",
"MD5"
]
},
"signSecret": "daasdad",
"extra": {},
"timestamp": "1604477618279"
}
参数名称 | 说明 |
code | 响应编码,接口正常为0 |
message | 响应信息 |
access_token | 访问令牌 |
token_type | 访问令牌类型,固定:bearer |
refresh_token | 刷新令牌 |
expires_in | 访问令牌有效时间(秒) |
signType | 签名类型,目前支持三种签名算法: HMAC_SHA256,SHA256,MD5 |
timestamp | 响应时间戳 |
signSecret | 签名密钥:生命周期与access_token一致,用于签名计算 |
access_token的有效期为12小时,所以您需要定期刷新access_token来保证您的access_token有效性。
REST URL
POST https://sdkapi.xylink.com/admin/login/refresh_token
参数说明:
参数 | 参数类型 | 参数位置 | 是否必须 | 默认值 | 说明 |
x-xy-clientid | String | Header | 是 | 无 | 小鱼向三方提供的应用id |
refresh_token | String | Body | 是 | 无 | 在第一步获取access_token时返回的refresh_token,用于主动更新access_token |
请求体示例(Json)
{
"refresh_token":"fb24d3e4-1aa1-4d29-8373-7c0c3a1fdd88"
}
请求成功结果示例:
{
"code": 0,
"message": "success",
"path": "",
"data": {
"access_token": "656158dd-f901-44b9-bf19-6eb0916f868d",
"token_type": "bearer",
"refresh_token": "fb24d3e4-1aa1-4d29-8373-7c0c3a1fdd88",
"expires_in": 43199,
"scope": "userProfile",
"signType": [
"HMAC_SHA256",
"SHA256",
"MD5"
],
"signSecret": "daasdad",
},
"extra": {},
"timestamp": "1608627159459"
}
参数名称 | 说明 |
code | 响应编码,接口正常为0 |
message | 响应信息 |
access_token | 访问令牌:签名计算必要 |
token_type | 访问令牌类型,固定:bearer |
refresh_token | 刷新令牌 |
expires_in | 访问令牌有效时间(秒) |
signType | 签名类型,目前支持三种签名算法: HMAC_SHA256,SHA256,MD5 |
timestamp | 响应时间戳 |
signSecret | 签名密钥:生命周期与access_token一致 |
待签名的字符串规则如下:
待签名的字符串 =
请求方法 + '\n' +
参与签名计算的header公共参数字符串+ '\n' +
请求URI和Query参数 + '\n' +
MD5加密后的请求体 + '\n' +
signSecret的值+'&
假设您要调用的REST API是创建云会议室号,则需要按如下步骤创建待签名的字符串:
3.1.1 HTTP请求方法(GET、PUT、POST、DELETE等),后跟换行符。
POST + "\n"
3.1.2 需要参与签名计算的公共参数, 组成 Header 签名串时,参与签名的参数按参数名做字典序升序排列,后跟换行符
"x-xy-clientid="+"ECHSG3HQwswdYs9HordpijT"+"&x-xy-nonce="+"KMnp7E1elFh24crhuKQ17TLOAEJliM24fdguiefydjshjvhdfsjhfjks"+"&x-xy-signtype="+"HMAC_SHA256"+"&x-xy-timestamp="+"1634786636372"+ "\n"
3.1.3 请求URI和Query参数,后跟换行符
/api/rest/external/v1/create_meeting?enterpriseId=KMnp7E1elFh24crhuKQ17TLOAEJl + "\n"
3.1.4 MD5加密请求体,后跟换行符
MD5("{\"meetingName\": \"my first cloudRoom\"}".getBytes()) + "\n"
3.1.5 追加signSecret + &
9edd11d6a93f43058a0b493adfe9a369&
3.1.6 生成的待签名字符
POST
x-xy-clientid=ECHSG3HQwswdYs9HordpijT&x-xy-nonce=KMnp7E1elFh24crhuKQ17TLOAEJliM24fdguiefydjshjvhdfsjhfjks&x-xy-signtype=HMAC_SHA256&x-xy-timestamp=1634786636372
/api/rest/external/v1/create_meeting?enterpriseId=KMnp7E1elFh24crhuKQ17TLOAEJl
fe22489187f216ab91ebc215656f1cf5
9edd11d6a93f43058a0b493adfe9a369&
将待签名字符串加密(加密方式有:MD5、SHA256、HMAC_SHA256),如使用HMAC_SHA256方式加密,则需要将signSecret+&作为加密key,得到最终签名
D953461B0E419646F560A3C74D18608AEBE417CD660363CEB723ADC6C1A9B64
java 代码计算签名的方法如下(参考),如需依赖包请自行导入工程:
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class SignUtilV2 {
public static final String SIGN_SIGN_KEY_NEW = "x-xy-sign";
public static final String SIGN_SIGN_TYPE_KEY_NEW = "x-xy-signtype";
public static String signSecret = "57f8f11d23f39a9d78d35af641cb";
public static String x_xy_clientid = "1U3GwtFH0VxHJGJHlCm8EMCI";
public static String access_token = "f12570f3-5146-44324-b658-48e3b42a19e4";
public static String x_xy_signtype = SignType.HMAC_SHA256.name();
/**
*test
*@param args
*/
public static void main(String[] args) {
String method = "POST";
String uri = "/api/rest/external/v1/create_meeting?enterpriseId=12f52fdahj2cd5c46825e5b7926d23663b9";
String requestBody = "{\"meetingName\": \"my first cloudRoom\"}";
Map<String, String> headersMap = new HashMap<>(16);
headersMap.put("x-xy-nonce", RandomStringUtils.random(60, true, false));
headersMap.put("x-xy-timestamp", String.valueOf(System.currentTimeMillis()));
headersMap.put("x-xy-clientid", x_xy_clientid);
headersMap.put("x-xy-signtype", x_xy_signtype);
byte[] requestBodyBytes = requestBody.getBytes();
// 加密密钥
String encryptionKey = signSecret + "&";
// 签名计算
String sign = SignUtilV2.getSignV2(
method,
headersMap,
uri,
requestBodyBytes,
encryptionKey);
System.out.println("最终签名:"+sign);
headersMap.put("Authorization","Bearer "+access_token);
headersMap.put(SIGN_SIGN_KEY_NEW,sign);
System.out.println("最终header:"+headersMap);
}
/**
*计算签名
* @param method
* @param headersMap
* @param uri
* @param requestBodyBytes
*@param encryptionKey 加密密钥
* @return
*/
private static String getSignV2(String method, Map<String, String> headersMap, String uri, byte[] requestBodyBytes, String encryptionKey) {
if (headersMap == null) {
return "";
}
StringBuilder sb = new StringBuilder();
//1.添加method
sb.append(method).append("\n");
//header排序
Set<String> keySet = headersMap.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
//2.添加Headers
for (String k : keyArray) {
if (k.equals(SIGN_SIGN_KEY_NEW)) {
continue;
}
Object param = headersMap.get(k);
String paramStr = String.valueOf(param);
if (paramStr.trim().length() > 0) {
// 参数值为空,则不参与签名
sb.append(k).append("=").append(paramStr.trim()).append("&");
}
}
sb = sb.deleteCharAt(sb.length()-1);//去除最后一个&
sb.append("\n");
//3.添加uri
sb.append(uri);
//4.添加请求的body
sb.append("\n");
if(requestBodyBytes != null && requestBodyBytes.length > 0) {
sb.append(DigestUtils.md5Hex(requestBodyBytes));
}else{
sb.append(DigestUtils.md5Hex("".getBytes(Charset.forName("UTF-8"))));
}
//5.添加密钥(signSecret + "&")
sb.append("\n");
sb.append(encryptionKey);
System.out.println("======================sign string=============================");
System.out.println(sb.toString());
System.out.println("======================sign string=============================");
String signStr = "";
//加密
SignType type = null;
String signType = headersMap.get(SIGN_SIGN_TYPE_KEY_NEW);
if (signType != null && signType.length() > 0) {
type = SignType.valueOf(signType);
}
if (type == null) {
type = SignType.MD5;
}
switch (type) {
case MD5:
signStr = DigestUtils.md5Hex(sb.toString()).toUpperCase();
break;
case SHA256:
signStr = DigestUtils.sha256Hex(sb.toString()).toUpperCase();
break;
case HMAC_SHA256:
signStr = sha256HMAC(sb.toString(),encryptionKey).toUpperCase();
break;
default:
break;
}
return signStr;
}
/**
* sha256_HMAC加密
* @param message 消息
* @param secret 秘钥
* @return 加密后字符串
*/
public static String sha256HMAC(String message, String secret) {
String hash = "";
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] bytes = sha256_HMAC.doFinal(message.getBytes("UTF-8"));
hash = byteArrayToHexString(bytes);
} catch (Exception e) {
}
return hash;
}
/**
* 将加密后的字节数组转换成字符串
*
* @param b 字节数组
* @return 字符串
*/
public static String byteArrayToHexString(byte[] b) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; b!=null && n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0XFF);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toLowerCase();
}
public enum SignType {
MD5,
SHA256,
HMAC_SHA256;
public static boolean contains(String type) {
for (SignType typeEnum : SignType.values()) {
if (typeEnum.name().equals(type)) {
return true;
}
}
return false;
}
}
}
公共参数:即在每次在进行API请求的时候,以下参数都需要携带
参数 | 参数类型 | 必须 | 参数位置 | 默认值 | 是否参与签名计算 | 说明 |
enterpriseId | String | 是 | Query | 无 | 是 | 企业Id |
x-xy-clientid | String | 是 | Header | 无 | 是 | 小鱼向三方提供的应用id |
x-xy-nonce | String | 是 | Header | 无 | 是 | 随机数 , 用于防止重复攻击,确保特定客户端随机数的唯一性,15分钟内不可重复,最大长度100 |
x-xy-timestamp | String | 是 | Header | 无 | 是 | 请求时间戳 |
x-xy-signtype | String | 是 | Header | 无 | 是 | 签名加密类型支持:HMAC_SHA256、SHA256、MD5 |
Authorization | String | 是 | Header | 无 | 否 | 获取到的访问令牌access_token字段的值(传入Bearer+空格+access_token返回值) |
x-xy-sign | String | 是 | Header | 无 | 否 | 签名计算得到的签名(大写) |