场景:老板突然一个需求发到我,需要将分销商的扫码内容解码获取到真实的商品条码(由于分销商使用了阿里的数据转换API服务,所以要解码)
一、调用阿里API的方式(文档地址)
1.使用简单认证(AppCode)方式调用API
2.使用SDK调用API
3.使用摘要签名认证方式调用API(本篇主要讲这个方式)
二、生成签名
1.准备API的 AppKey、AppSecret
2.客户端生成签名一共分三步处理:
2.1.从下面7个字段构成整个签名串,字段之间使用\n间隔换行,如果Headers为空,则不需要加\n,其他字段如果传空则必须要保留\n(关键点)
HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParameters
- HTTPMethod:HTTP的方法,全部大写,比如POST
- Accept:请求中的Accept头的值,可为空,可传
application/json; charset=utf-8
- Content-MD5:请求中的Content-MD5头的值,可为空
- Content-Type:请求中的Content-Type头的值,可为空,可传
application/x-www-form-urlencoded; charset=UTF-8
- Date:请求中的Date头的值,可为空
- Headers:用户可以选取指定的header参与签名,关于header的签名串拼接方式有以下规则:参与签名计算的Header的Key按照字典排序后使用如下方式拼接:
HeaderKey1 + ":" + HeaderValue1 + "\n" + HeaderKey2 + ":" + HeaderValue2 + "\n" + ... HeaderKeyN + ":" + HeaderValueN + "\n"
某个Header的Value为空,则使用HeaderKey+":"+"\n"参与签名,需要保留Key和英文冒号
所有参与签名的Header的Key的集合使用英文逗号分割放到Key为X-Ca-Signature-Headers的 Header中
以下Header不参与Header签名计算:X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date;
- PathAndParameters这个字段包含Path,Query和Form中的所有参数,具体组织形式如下:
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN
Query和Form参数对的Key按照字典排序后使用上面的方式拼接;
Query和Form参数为空时,则直接使用Path,不需要添加?;
参数的Value为空时只保留Key参与签名,等号不需要再加入签名;
Query和Form存在数组参数时(key相同,value不同的参数) ,取第一个Value参与签名计算。
2.2.使用HmacSHA256加密算法加AppSecret 对签名字符串进行加密处理,得到签名
3.将签名字符串所相关的所有头加入到原始HTTP请求中,得到最终HTTP请求
三、不废话 上代码
1.准备工作
//阿里API AppKey
public final static String appKey = "xxxxx";
//阿里API AppSecret
public final static String appSecret = "xxxxxxxxx";
//请求参数
public final static String queryParams = "xxxxxxx";
//阿里API
public final static String ALiBaBaApi = "https://api.ailend.top/api/v1x/xxxxx/xxxxxx?x="+queryParams;
//签名串的7个字段
public final static String HTTPMethod = "POST";
public final static String Accept = "application/json; charset=utf-8";
public final static String Content_MD5 = "";
public final static String Content_Type = "application/x-www-form-urlencoded; charset=UTF-8";
public final static String Date = "";
public final static String Headers = "X-Ca-Key:%s" +"\n"+
"X-Ca-Nonce:%s" +"\n"+
"X-Ca-Signature-Method:HmacSHA256" +"\n"+
"X-Ca-Timestamp:%s";
public static String PathAndParameters = "";
2.组装签名串
/*
* 组装签名串
* @params uuid 随机数
* @params url 请求的阿里API
* @params timestamp 时间戳
* */
public static String packageSignStr(String uuid,String url,long timestamp){
URL parseUrl = URLUtil.url(url);
PathAndParameters = parseUrl.getPath()+"?"+parseUrl.getQuery();
String temp = HTTPMethod +"\n"+
Accept +"\n"+
Content_MD5 +"\n"+
Content_Type +"\n"+
Date +"\n"+
Headers +"\n"+
PathAndParameters;
return String.format(temp,appKey, uuid, timestamp);
}
3.生成签名的方法
/*
* 生成加密签名
* @params content 组装好的签名串
* @params appSecret 密钥
* */
private static String getSHA256StrJava(String content, String appSecret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(StrUtil.bytes(appSecret, CharsetUtil.UTF_8), "HmacSHA256");
mac.init(secret_key);
byte[] binaryData = mac.doFinal(StrUtil.bytes(content, CharsetUtil.UTF_8));
return Base64.getEncoder().encodeToString(binaryData);
}
4.调用生成
String uuid = UUID.randomUUID().toString();
long l = System.currentTimeMillis();
//生成签名
String sign = getSHA256StrJava(packageSignStr(uuid, ALiBaBaApi,l),appSecret);
//请求头加入签名
String body = HttpUtil.createPost(ALiBaBaApi)
.header("Accept", "application/json; charset=utf-8")
.header("Content-MD5", "")
.header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("Date", "")
.header("X-Ca-Key", appKey)
.header("X-Ca-Nonce", uuid)
.header("X-Ca-Signature-Method", "HmacSHA256")
.header("X-Ca-Timestamp", String.valueOf(l))
.header("X-Ca-Signature-Headers", "X-Ca-Key,X-Ca-Nonce,X-Ca-Signature-Method,X-Ca-Timestamp")
.header("X-Ca-Signature", sign)
.execute().body();
System.out.println("requestRes : "+body);
四、签名排错
当签名不匹配时网关会通过 HTTP Response Header 中的 X-Ca-Error-Messaqe 返回服务端参与签名计算的 StringToSign.
我们只需要打印出 packageSignStr() 方法生成的签名串 ,然后与响应头中的 X-Ca-Error-Messaqe 中值进行比对即可,找出哪里不同,针对性解决就OK了,多一个字符、少一个字符、多一个换行、少一个换行都会返回签名无效,注意:响应头中的#为换行符\n
如果服务端与客户端的StringToSign一致请检查用于签名计算的APP Secret是否正确。