鉴权认证机制
1、简介
本文档主要针对HTTP API调用者,云从AI开放平台使用OAuth2.0授权调用开放API,签名值 sign 是将请求源串以及密钥根据一定签名方法生成的签名值,用来提高传输过程参数的防篡改性;秘钥的获取及签名的生成方法如下:
2、秘钥获取
(1) 登录 https://ai.cloudwalk.com ,若实名制通过后,在资源主页——新建应用,每一个新建完成的应用可获取一组 appKey/appSecret;
(2) 若还未注册,请注册并实名制审核通过后,按照第一步可获取。
3、获取签名算法流程
请求 host 地址
https://api-ai.cloudwalk.com/
国密加密算法请求 host 地址
https://sm-api-ai.cloudwalk.com/
以人脸比对接口为例:
https://api-ai.cloudwalk.com/ai-cloud-face/face/tool/compare
Step 1. 获取AppKey和AppSecret
在云从AI开放平台注册并实名制后,需要在资源主页中选择其中一项资源服务进行应用创建,应用创建完成后,将会为应用自动生成一组秘钥(AK,AS): 在这里Appkey/AppSecret;这里分别假设为:
appKey = 66e255ab40ed2bcb600a8b443a3ea7eb
appSecret = ckKU7P4FwB4P
Step 2. 拼接有效签名串
第一步:将除sign外的所有请求参数放入集合M中,之后向此集合Map中加入uri不含host),这里uri示例为
/ai-cloud-face/face/tool/compare
第二步: 将第一步得到的集合M中的参数按照参数名ASCII码从小到大排序(字典序);
第三步:将第二步中排序后的key和与之对应的value拼接起来,使用URL键值对的格式(即key1=value1&key2=value2…)得到一个字串,下文称之为authinfo。
注意以下规则:
- 参数名ASCII码从小到大排序(字典序)
- 参数名区分大小写
- 传送的sign参数不参与签名,将生成的签名与该sign值作校验
- 云从接口可能增加字段,验证签名时必须支持增加的扩展字段
- uri只参与签名,无需在请求中添加该参数
- base64的协议rfc1521,后期会做更新
authinfo示例如下:
每个接口每次调用之前都需要重新按照请求参数进行拼接authinfo
authinfo = "appKey=1c6a6c26e16fedaf1cd2658cb62fcba3&imgA=/9j/4AAQSkZJRgABAQEAYABgAAD...&imgB=/9j/4AAQSkZJRgABAQEAYABgAAF...&nonceStr=12345678&uri=ai-cloud-face/face/tool/compare"
其中:
- appKey 为应用生成的 AppKey
- nonceStr 为随机串,用户需自行生成。
- imgA、imgB 为业务参数,此处是图像的业务参数,为待处理图片(base64编码)
- uri 为 /ai-cloud-face/face/tool/compare
- 图片的base64编码需要去头部
Step 3. 使用authinfo生成签名串
使用hmacsha1算法对请求进行签名;签名串需要使用base64编码。 根据签名方法
sign= Base64(HMAC-SHA1(authinfo, appSecret))
其中AppSecret为在开放平台应用创建时生成的AppSecret.
Java 代码 示例 :
// 此代码片段仅为一个示例,请勿直接copy使用,为了体现传参的不同,示例为“人脸检测”的JAVA代码示例
public class FaceDetectDemo {
private static final String CONTENT_CHARSET = "UTF-8";
private static final String secret = "xxx";
private static final String appKey = "xxx";
private static String uri = "/ai-cloud-face/face/tool/detect";
public static void main(String[] args) {
try {
Map<Object, Object> parameters = new HashMap<>();
String nonceStr = "12345678";
parameters.put("appKey", appKey);
parameters.put("nonceStr", nonceStr);
parameters.put("img", Base64.getEncoder().encodeToString(IOUtils.toByteArray(new URL("http://xxxxxxxx.jpg").openStream())));
parameters.put("sign", createSign(parameters));
HttpUtil.post("https://api-ai.cloudwalk.com" + uri, JSONObject.toJSONString(parameters));
} catch (Exception e) {
e.printStackTrace();
}
}
private static String createSign(Map<Object, Object> parameters) throws Exception {
SortedMap<Object, Object> parametersForSign = new TreeMap<>();
parametersForSign.put("uri", uri);
parametersForSign.putAll(parameters);
StringBuilder sb = new StringBuilder();
Set es = parametersForSign.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
if (!it.hasNext()) {
sb.append(k).append("=").append(v);
} else {
sb.append(k).append("=").append(v).append("&");
}
}
}
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), mac.getAlgorithm());
mac.init(secretKey);
byte[] hash = mac.doFinal(sb.toString().getBytes(CONTENT_CHARSET));
return new BASE64Encoder().encode(hash);
}
}
使用其它程序设计语言开发时 可用上面示例中的原文进行签名验证 得到的签名 串与例子中的一致即可。
4、联网鉴身服务支持国密算法进行加密
在平台侧选择国密算法后,接口sign(签名)需使用SM2算法加密、业务数据使用SM4算法加解密
4.1 密钥获取
在联网鉴身应用创建、编辑页面可选SM2_SM4
算法选项
- 选择
SM2_SM4
加密算法后在应用详情页可额外获取SM2 publicKey
、SM4 Secret
两组密钥 - 其中
SM2 publicKey
对应SM2算法的公钥,SM4 Secret
对应的是SM4算法的密钥 - 密钥为HEX(16进制)字符串,
SM2 publicKey
共128字符64字节、SM4 Secret
共32字符16字节 - 密钥使用标准长度(例如:BC库需要
04
标识头需自行处理)
4.2 参数说明
国密算法加密过程将请求参数分为公共参数
以及业务参数
两部分,业务参数使用SM4
加密,同时使用SM2
、SM3
算法替换hmacsha1
对生成的authinfo
进行加密操作来生成签名
公共参数
- 包含三个固定KEY
nonceStr
、uri
以及appKey
- appKey 为应用生成的 appKey
- nonceStr 为随机串,用户需自行生成
- uri 为 /ai-cloud-cweis/netCheck/checkFaceNew
- 包含三个固定KEY
业务参数
- 接口文档中各个接口定义的请求参数中除公共参数以外的所有业务参数
4.3 生成签名及请求数据BODY
第一步:将除sign外的所有公共请求参数放入集合M中
{
// 公共参数
"appKey" : "xxxxx",
"nonceStr": "12345678",
"uri": "/ai-cloud-cweis/netCheck/checkFaceNew"
}
第二步:将所有业务参数放入集合N中,
// apiParams(集合N)
{
"cId": 123123,
"cName": "yc",
"img": base64(xxx.png) // 图片统一使用BASE64编码
}
第三步:将公共参数(集合M)与业务参数(集合N)合并成签名集合
// 签名集合
{
// 公共参数
"appKey" : "xxxxx",
"nonceStr": "12345678",
"uri": "/ai-cloud-cweis/netCheck/checkFaceNew"
//业务参数
"cId": 123123,
"cName": "yc",
"img": base64(xxx.png) // 图片统一使用BASE64编码
}
第四步:通过第三步生成的签名集合来生成authinfo,将排序后的key和与之对应的value拼接起来,使用URL键值对的格式(即key1=value1&key2=value2…)
注意以下规则:
- 参数名ASCII码从小到大排序(字典序)
- 参数名区分大小写
authinfo示例如下:
每个接口每次调用之前都需要重新按照请求参数进行拼接authinfo
authinfo = "appKey=1c6a6c26e16fedaf1cd2658cb62fcba3&cId=123123&cName=yc&img=/9j/4AAQSkZJRgABAQEAYABgAAD...&nonceStr=12345678&uri=ai-cloud-face/face/tool/compare"
第五步:将第四步生成的authinfo通过SM3
加密生成摘要字符串,再对生成的摘要进行SM2
加密运算生成签名,后面需要将签名拼接到请求的参数中,其中SM2使用公钥加密,mode使用C1C2C3
(至此完成签名的生成工作)
String lol = SmUtil.sm3(authinfo);
String sign = sm2.encryptHex(lol, KeyType.PublicKey)
第六步:对业务参数集合N进行SM4算法加密[ECB模式], 结果为HEX字符串
> SmUtil.sm4(HexUtil.decodeHex(sm4Secret)).encryptHex(JSONObject.toJSONBytes(apiParams))
第七步:将第六步得到的业务参数内容使用KEY[content]组装成集合{"content" : "value"}
,其中value为第六步后的结果值
// content内容为[集合N]通过`SM4`加密后`HEX`编码值,SM4使用ECB模式
{
"content": "04277a370b9182a4291088bf071ef7c00302c4c831d698c0b203264d2471e5b5031db4ad..."
}
第八步:将第七步生成的[content]集合和第一步公共参数(集合M)以及第五步生成的签名合并成请求BODY
// 最终的请求JSON数据
{
// 公共参数
"appKey" : "xxxxx",
"nonceStr": "12345678",
"uri": "/ai-cloud-cweis/netCheck/checkFaceNew",
// 加密后的业务参数(值为第六步通过业务参数加密后的数据)
"content": "04277a370b9182a4291088bf071ef7c00302c4c831d698c0b203264d2471e5b5031db4ad...",
// 签名(值为第五步通过authinfo计算的签名值)
"sign": "57fdec6f770bceb3c060a68ed762eb1941daa005f14a20f5c27ebcb231..."
}
}
4.4 响应数据解密
选择国密算法后,服务端会自动为响应JSON数据中的data字段进行SM4
加密,使用前需要进行解密操作,其他字段则正常返回
// 响应数据(解密前):
{
"code": 0,
"message": "请求成功",
"data": "57fdec6f770bceb3c060a68ed762eb1941daa005f14a20f5c27ebcb231"
}
// 响应数据(解密后):
{
"code": 0,
"message": "请求成功",
"data": {
"score": 0.15,
"flowId": null
}
}
SmUtil.sm4(HexUtil.decodeHex(sm4Secret)).decryptStr(response.get("data"))
说明
* SM4使用ECB模式
Java 代码 完整的SM2_SM4请求示例 :
// 此代码片段仅为一个示例,请勿直接copy使用,为了体现传参的不同,示例为“联网鉴身”的JAVA代码示例
public class GMEncryptUtil {
// 应用标识
static String appKey = "158c0a3c85e2a5a8f97405f14cb44b81";
//sm2 公钥
static String sm2Pub = "c715a80a5cae5b4a8f3b30da55acd12c1aac091620c644588d15273dd30b7c9957d08d96461f24a8dbcbeb40af3d87c3becd98e258541680939e5cc27adc058f";
// uri
private static String uri = "/ai-cloud-cweis/netCheck/checkFaceNew";
private static String url = "https://ai-test.cloudwalk.work";
//sm4 密钥
public static String sm4Secret = "dbf40c597cdca76c75f1f76ea762d98c";
private static String createSign(Map<Object, Object> parameters) throws Exception {
// [第四步]计算authinfo
SortedMap<Object, Object> parametersForSign = new TreeMap<>();
parametersForSign.putAll(parameters);
StringBuilder sb = new StringBuilder();
Set es = parametersForSign.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
if (!it.hasNext()) {
sb.append(k).append("=").append(v);
} else {
sb.append(k).append("=").append(v).append("&");
}
}
}
// 对authinfo进行sm3加密生成摘要信息
String lol = SmUtil.sm3(sb.toString());
// 初始化SM2实例,对摘要进行SM2加密生成sign
SM2 sm2 = new SM2(null, ECKeyUtil.toSm2PublicParams("04".concat(sm2Pub)));
sm2.usePlainEncoding();
// mode使用C1C2C3
sm2.setMode(SM2Engine.Mode.C1C2C3);
// [第五步]生成签名
return sm2.encryptHex(lol, KeyType.PublicKey);
}
public static void main(String[] args) {
try {
// [第一步]公共参数
Map<Object, Object> pubParams = new HashMap<>();
String nonceStr = "12345678";
pubParams.put("appKey", appKey);
pubParams.put("nonceStr", nonceStr);
pubParams.put("uri", uri);
// [第二步]业务参数
Map<Object, Object> apiParams = new HashMap<>();
apiParams.put("cId", "440825199409153488");
apiParams.put("cName", "云从");
// 图片使用base64编码
apiParams.put("img", Base64.encode(IOUtils.toByteArray(new URL("https://cloudwalk.com/uploads/20200609/6db7fff7774b9c4dca84000d59af12ca.png").openStream())));
// [第三部]拼接签名集合
Map<Object, Object> sign = new HashMap<>(pubParams);
sign.putAll(apiParams);
// [第四步/第五步]通过createSign方法生成签名,将签名sign拼接到请求的body中
pubParams.put("sign", createSign(sign));
// [第六步/第七步]SM4将业务参数加密后,拼接到请求body中
String contentValue = SmUtil.sm4(HexUtil.decodeHex(sm4Secret)).encryptHex(JSONObject.toJSONBytes(apiParams));
pubParams.put("content", contentValue);
// [第八步]此例中pubParams为生成的请求body集合
HttpPost httpPost = new HttpPost(url + uri);
httpPost.setHeader("Content-Type", "application/json");
StringEntity entity = new StringEntity(JSONObject.toJSONString(pubParams), StandardCharsets.UTF_8);
httpPost.setEntity(entity);
CloseableHttpClient client = HttpClients.createDefault();
String response = EntityUtils.toString(client.execute(httpPost).getEntity(), "UTF-8");
// 响应数据中key[data]为核心数据会进行SM4加密,使用时需先解密处理
JSONObject jsonObject = (JSONObject) JSONObject.parse(response);
if (jsonObject.containsKey("data") && jsonObject.get("data") != null) {
String d = (String) jsonObject.get("data");
jsonObject.put("data", JSONObject.parseObject(SmUtil.sm4(HexUtil.decodeHex(sm4Secret)).decryptStr(d)));
}
System.out.println(JSONObject.toJSONString(jsonObject));
} catch (Exception e) {
e.printStackTrace();
}
}
}