AIOT云云推送鉴权
更新时间:2024-09-20
AIOT云云推送鉴权
概述
在整体服务接入后,若需要对设备交互过程进行干预,则需要进云云对接,百度侧将设备交互的消息载荷按照特定协议推送到目标服务,目标服务处理后返回,后续将下发给用户设备。
API签名认证机制
客户端建立连接时,需要在 header 传递 Timestamp, AccessKey, Authorization 参数。
名词 | 描述 |
---|---|
Timestamp | 系统时间戳(示例中时间需要在5分钟内,否则为失效) |
ACCESS_KEY | 百度侧提供访问key |
SECRET_KEY | 百度侧提供密钥key |
RequestBody | 请求体实际载荷 |
Authorization | Authorization参数签名过程:整个签名摘要使用HMAC-SHA256算法,将ACCESS_KEY + Timestamp + RequestBody 进行摘要计算并且进行Base64编码,点击下载完整示例代码 |
接口说明
1. 通信协议
数据交换格式为JSON,所有request/response body内容均采用UTF-8编码。
请求参数包括如下4种:
参数类型 | 说明 |
---|---|
URI | 通常用于指明操作实体,如:POST /v1/api/foo |
Query参数 | URL中携带的请求参数,通常用来指明要对实体进行的动作 |
HEADER | 通过HTTP头域传入,Timestamp, AccessKey, Authorization |
RequestBody | 通过JSON格式组织的请求数据体 |
2. 请求参数
与 ASR 结果回调接口的参数一致
Header 参数
参数 | 类型 | 说明 |
---|---|---|
Content-Type | string | application/json |
Timestamp | long | 时间戳(毫秒) |
AccessKey | string | access_key |
Authorization | string | 摘要信息 |
Body 参数
参数 | 类型 | 说明 |
---|---|---|
logld | string | 日志 ID |
device | obj | 百度三元组数据 |
device.fc | string | |
device.pk | string | |
device.ak | string | |
query | string | ASR 识别结果 |
nlulnfos | string | 请求语义 |
extlnfo | obj | 扩展信息 |
custom | string | 端上自定义信息 |
nluInfos 字段说明 //这里只是示例
[{\"domain\":\"clean_bot\",\"intent\":\"start_clean\",\"slots\":{\"location\":[{\"slot_type\":\"STRING\",\"text\":\"卧室\",\"value\":\"卧室\"}]}}]
extInfo 字段说明 //这里只是示例
{
"memberId": 123456789, // 用户ID
}
3. 响应参数
logId | string | 日志 ID |
errcode | int | 错误码,0为成功,其他为失败 |
errmsg | string | 错误信息 |
tts | obj | 播报信息 |
nlulnfos | string | 响应语义 |
ctrlparams | obj | 控制参数 |
custom | string | 客户自定义,透传到端上 |
tts 内容字段说明:
{
"flag": 0, // 0: 正常播报(默认),1:tts 不进行播报(content字段内容不生效)
"content":"tts text" // 用于tts播报的内容, 如果content为空,则会用百度侧的content进行播报
}
ctrlParams 字段说明:
{
"mediaPlay": 0 // 0: 正常播报(默认),1:不进行播报
}
custom字段说明:
类型为string,百度透传结果,客户进行端云协同
原始如json数据需要序列化成 string类型
举例:
"custom": "
{\"otherKey1\":\"otherValue1\",\"otherKey2\":\"otherValue2\"}
". //整个内容是 string,百度透传结果,客户进行端云协同
4. 状态码
errcode | 说明 |
---|---|
0 | 成功 |
1001 | 鉴权失败 |
1002 | 参数错误 |
1003 | 内部错误 |
示例代码
实例代码块为校验签名的合法性,实际调用为百度侧进行签名,客户侧校验百度侧签名是否合法。点击下载完整示例代码
签名校验核心逻辑
{
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.security.InvalidKeyException;
/**
* 验签工具类. 验证签名是否正确
* 实现方需要实现以下策略
* <p>
* 1.时间有效性判断(建议上下5分钟之内认为有效)
* 2.防重攻击.
* 3.签名有效性判断
**/
public class CloudSignature {
/**
* ak参数.
*/
public static final String ACCESS_KEY = "QRtM5Mqc3Knn6duZd9xTjjfbSSlIDwln";
/**
* sk参数.由双方云共同拥有. 不进行网络传输.请严格保密.
*/
public static final String SECRET_KEY = "dEtj0KZebuj6gadtg5zNdVmXBWryNUmDzbFOM7fqcKzT3Excx8lyIgsyOUPceB5nS3NlJ5LJ9TqsElx8KyC8UD3izVL0eGw5tCm44YxuV8SDPfhms5X862PtnG6MPDlf";
/**
* 验证签名
*
* @param timestamp 系统时间戳-毫秒
* @param signature 签名
* @param requestBody 请求内容.HTTP请求和HTTPS请求里的最原始的Body String 字符串
* @return 是否合法
*/
public static boolean authenticateRequest(String timestamp, String signature, String requestBody) {
// 时间戳判断-时间有效性判断(建议上下5分钟之内认为有效)
long now = System.currentTimeMillis();
long currentTime = Long.parseLong(timestamp);
// 绝对值小于5分钟之内认为有效
long timeDiff = Math.abs(now - currentTime);
System.out.println(timeDiff);
if (timeDiff > 5 * 50 * 1000) {
return false; // 时间不在5分钟之内认为无效
}
// 防重判断. 这里暂时不实现
// 可以利用 requestBody 里的 logId 作为防重key
// 签名校验
String calculatedSignature = calculateSignature(timestamp, ACCESS_KEY, requestBody); // 根据具体算法计算请求签名
// 如果请求签名不匹配,说明请求未经授权,拒绝鉴权
return calculatedSignature == null || calculatedSignature.equals(signature);
// 鉴权通过
}
/**
* 计算签名
*
* @param timestamp 时间戳
* @param accessKey ak
* @param requestBody 请求内容.是HTTP请求和HTTPS请求里的最原始的Body String 字符串
* @return 签名
*/
public static String calculateSignature(String timestamp, String accessKey, String requestBody) {
try {
// 使用HMAC-SHA256算法进行签名计算
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
String toBeSigned = accessKey + timestamp + requestBody; // 将请求内容body加入签名计算
byte[] signatureBytes = mac.doFinal(toBeSigned.getBytes(StandardCharsets.UTF_8));
// 将签名结果进行Base64编码
return Base64.getEncoder().encodeToString(signatureBytes);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
}
}
}
测试代码
import org.junit.Assert;
import org.junit.Test;
public class CloudSignatureTest {
@Test
public void normalTest() {
// 示例使用:
String timestamp = System.currentTimeMillis() + ""; // 获取当前时间戳
String accessKey = CloudSignature.ACCESS_KEY; // 替换为实际的Access Key
String requestBody = "{\"logId\":\"123\",\"device\":{\"fc\":\"fc\",\"pk\":\"pk\",\"ak\":\"000000000019\"},\"query\":\"测试query\",\"nluInfos\":\"[{\\\"domain\\\":\\\"unknown\\\",\\\"intent\\\":\\\"unknown\\\",\\\"slots\\\":{}}]\"}"; // 替换为实际的请求内容body
String signature = CloudSignature.calculateSignature(timestamp, accessKey, requestBody); // 计算请求签名
// 将timestamp、accessKey、requestBody和signature kJcAI4FSIFvL6nY7uhjQ6A4THy+0sPLKd6LLcR90Ozk=
System.out.println(signature);
Assert.assertTrue(CloudSignature.authenticateRequest(timestamp, signature, requestBody));
}
@Test
public void fakerSignatureTest() {
// 示例使用:
String timestamp = System.currentTimeMillis() + ""; // 获取当前时间戳
String requestBody = "{\"logId\":\"123\",\"device\":{\"fc\":\"fc\",\"pk\":\"pk\",\"ak\":\"000000000019\"},\"query\":\"测试query\",\"nluInfos\":\"[{\\\"domain\\\":\\\"unknown\\\",\\\"intent\\\":\\\"unknown\\\",\\\"slots\\\":{}}]\"}"; // 替换为实际的请求内容body
// 异常的ak
String signature = CloudSignature.calculateSignature(timestamp, "faker_access_key", requestBody); // 计算请求签名
// 将timestamp、accessKey、requestBody和signature kJcAI4FSIFvL6nY7uhjQ6A4THy+0sPLKd6LLcR90Ozk=
System.out.println(signature);
Assert.assertFalse(CloudSignature.authenticateRequest(timestamp, signature, requestBody));
}
@Test
public void InvalidTimestampSignatureTest() {
// 示例使用:
String timestamp = (System.currentTimeMillis() + (60 * 1000 * 20)) + ""; // 获取当前时间戳
String accessKey = CloudSignature.ACCESS_KEY; // 替换为实际的Access Key
String requestBody = "{\"logId\":\"123\",\"device\":{\"fc\":\"fc\",\"pk\":\"pk\",\"ak\":\"000000000019\"},\"query\":\"测试query\",\"nluInfos\":\"[{\\\"domain\\\":\\\"unknown\\\",\\\"intent\\\":\\\"unknown\\\",\\\"slots\\\":{}}]\"}"; // 替换为实际的请求内容body
String signature = CloudSignature.calculateSignature(timestamp, accessKey, requestBody); // 计算请求签名
// 将timestamp、accessKey、requestBody和signature kJcAI4FSIFvL6nY7uhjQ6A4THy+0sPLKd6LLcR90Ozk=
System.out.println(signature);
Assert.assertFalse(CloudSignature.authenticateRequest(timestamp, signature, requestBody));
}
}