# Restful签名鉴权
Restful接口使用HTTP
无状态协议,并使用密钥签名的方式进行鉴权。平台整体API设计使用Restful风格,但某些特殊操作会根据情况不同使用适合的命名方式和方法,具体根据API文档进行调用。
# HTTP请求说明
# 请求方法
方法 | 说明 |
---|---|
GET | 用于获取数据信息,不能包含任何Body 信息。 |
POST | 用于增加数据,Header必须包含 Content-Type: application/json , Body 内容必须是JSON格式。 |
PUT | 用于修改数据,Header与Body同POST方法。 |
DELETE | 用于删除数据,不能包含Body 信息。 |
# 请求固定参数
以下参数为鉴权固定参数,每次请求均需要在query
中携带以下参数(非鉴权方法无需传递)。就是一般url中?
后面部分,例如:
// HTTP协议网关API
https://api.hanclouds.com/api/v1/pushsvcs/createAuthToken?ts=1531709593000&nonce=xxxxxxx&signature=xxxxx
// HTTP协议图片网关API
https://api.hanclouds.com/image/v1/devices/deviceKey/datastreams/dataName/images?ts=1531709593000&nonce=xxxxxxx&signature=xxxxx
参数 | 说明 |
---|---|
ts | API发送时间戳,单位是毫秒,服务器会判断该请求时间是否在允许范围内,避免重放攻击。允许波动范围为5分钟 |
nonce | 随机字符。 |
signature | 签名值,使用base64编码传递,具体签名算法参考后面文档。 |
# Key信息传递
服务器在接受请求时,需要知道当前您使用了什么级别
的鉴权参数,所以需要在Header
中固定传递相关信息。
授权类型 | Header参数 | 说明 |
---|---|---|
用户级 | HC-USER-KEY | 用户唯一key,描述使用哪位用户身份进行鉴权访问。与HC-USER-AUTH-KEY 共存。 |
用户级 | HC-USER-AUTH-KEY | 用户鉴权参数。 |
产品级 | HC-PRODUCT-KEY | 产品唯一key,描述使用哪个产品身份进行鉴权访问。与HC-PRODUCT-SERVICE-KEY 共存。 |
产品级 | HC-PRODUCT-SERVICE-KEY | 产品鉴权参数,可以是任何功能的一种。 |
设备级 | HC-DEVICE-KEY | 设备唯一key,描述使用哪个设备身份进行鉴权访问。 |
以上参数不需要全部传递,取决于当前您使用什么级别鉴权方式。
# 签名算法
# 签名字符串生成
以下用抽象语言描述
{key}={value}
的方式将所有query
参数进行拼接,放入字符串数组中。(signature为最后生成参数,请求参数中不能携带该参数,如有重名请修改)- 根据字符串ASCII码值进行正向排序。
- 将字符串数组数据使用
&
字符进行拼接。 - 如果有Body参数,直接将Body参数放到上述生成字符串最后。
在query参数中,如果只有key
而没有value
,不要放入字符串数组中,例如: aa=&ff=cc,aa不参与签名计算。
# 网关API签名
Java语言签名计算示例:
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.size() == 0) {
return null;
}
List<String> paramStrs = new ArrayList<>(parameterMap.size());
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
if (entry.getValue().length == 0) {
continue;
}
//签名参数不参与签名
if (entry.getKey().equals("signature")) {
continue;
}
for (String v : entry.getValue()) {
paramStrs.add(entry.getKey() + "=" + v);
}
}
Collections.sort(paramStrs);
StringBuilder signStrBuilder = new StringBuilder();
boolean isFirstLoop = true;
for (String paramStr : paramStrs) {
if (isFirstLoop) {
isFirstLoop = false;
} else {
signStrBuilder.append('&');
}
signStrBuilder.append(paramStr);
}
try {
byte[] bodyContent = StreamUtils.copyToByteArray(request.getInputStream());
signStrBuilder.append(new String(bodyContent, Charset.forName("UTF-8")));
} catch (IOException e) {
return null;
}
# 图片网关API签名
Java语言签名计算示例:
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.size() == 0) {
return null;
}
List<String> paramStrs = new ArrayList<>(parameterMap.size());
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
if (entry.getValue().length == 0) {
continue;
}
//签名参数不参与签名
if (entry.getKey().equals("signature")) {
continue;
}
for (String v : entry.getValue()) {
paramStrs.add(entry.getKey() + "=" + v);
}
}
Collections.sort(paramStrs);
StringBuilder signStrBuilder = new StringBuilder();
boolean isFirstLoop = true;
for (String paramStr : paramStrs) {
if (isFirstLoop) {
isFirstLoop = false;
} else {
signStrBuilder.append('&');
}
signStrBuilder.append(paramStr);
}
try {
//body参数
byte[] bodyContent = StreamUtils.copyToByteArray(request.getInputStream());
//图片网关API通过base64编码
signStrBuilder.append(Base64Utils.encodeToString(bodyContent));
} catch (IOException e) {
return null;
}
python语言签名计算示例:
import requests
import random
import string
import base64
import time
import hmac
from hashlib import sha1
import urllib
import os
def uploadimg(uploadToken, devicekey, jpgimgPath):
# 读取图片数据
file = open(jpgimgPath, "rb")
imgdata = file.read()
file.close()
# 组装请求参数
# 表示图片是jpg
getParm = ['imageType=1']
# ts
getParm.append("ts=" + str(round(time.time() * 1000)))
# 16个随机字符
nonce = ''.join(random.sample(string.ascii_letters + string.digits, 16))
getParm.append('nonce=' + nonce)
# 参数排序
getParm.sort()
# 拼接get请求参数
total_str = ""
for parm in getParm:
total_str = total_str + parm + "&"
# 图片数据base64编码
b64imgdata = base64.b64encode(imgdata)
imgstring = b64imgdata.decode('utf-8')
# 组成最终的预签名字符串
content = total_str[:-1] + imgstring
# 开始hmac的sha1签名
hmac_code = hmac.new(uploadToken.encode('utf-8'), content.encode('utf-8'), sha1).digest()
sign = base64.b64encode(hmac_code)
# 包含有signature的请求参数
# 务必要对base64后的签名经行url编码,否则里面的特殊符号影响请求
total_str = total_str + "signature=" + urllib.parse.quote(str(sign, "utf-8"))
url = 'https://api.hanclouds.com/image/v1/devices/' + devicekey + '/datastreams/img/images?' + total_str
# 组装设备级授权的header device-key
headers = {"Content-Type": "application/json", "HC-DEVICE-KEY": devicekey}
print("url = ", url)
print("headers = ", headers)
# 开始发送请求
req = requests.post(url, headers=headers, data=imgdata)
print("status = ", req.status_code)
print("result = ", req.content)
print("result = ", req.text.encode('utf8', 'ignore'))
uploadimg('WpptFiHQWH8zzEtT', '88a6dd41fddb4a1e8553d87cb5c948c2', 'C:\\res.jpg')
# HMAC-SHA1签名
大部分语言都实现了 HMAC-SHA1
算法,将上述生成的签名字符串
与对应的密钥(**Secret)
作为参数进行签名,并进行Base64编码,即可得到 signature
值。
Java 示例 代码:
public static String signWithHmacsh1(String secret, String content) {
try {
byte[] keyBytes = secret.getBytes("utf-8");
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] rawHmac = mac.doFinal(content.getBytes("utf-8"));
String hmacSHA1Encode = new String((new BASE64Encoder()).encode(rawHmac));
return hmacSHA1Encode;
} catch (Exception e) {
//异常处理
}
}