最近公司在做搜索,要求下拉提示使用阿里的下拉提示,其他都还好说,就是这个签名机制,弄了一下午,也是遇到了坑,特此记录。
//签名
public static final String ALGORITHM = "HmacSHA1";
public static final String ENCODING = "UTF-8";
//分隔符
public static final String SEPARATOR = "&";
/**
* 请求参数
*/
public OkRequestParams getRequestParams() {
OkRequestParams params = new OkRequestParams();
//公共参数
params.put("Version", "v2");
params.put("AccessKeyId", ACCESS_KEY_ID);
params.put("SignatureMethod", "HMAC-SHA1");
params.put("Timestamp", DateTimeUtil.local2Utc(System.currentTimeMillis()));
params.put("SignatureVersion", "1.0");
params.put("SignatureNonce", System.currentTimeMillis() + Util.getNewRandomStr(4));
//下拉提示请求参数
params.put("query", mSearchStr);
params.put("index_name", INDEX_NAME);
params.put("suggest_name", SUGGEST_NAME);
//签名:需要以上参数共同参与才能得出签名
params.put("Signature", getSignature(params).trim());
return params;
}
/**
* 根据请求参数获得签名
* @param urlParams
* @return
*/
private String getSignature(OkRequestParams urlParams) {
String signature = "";
try {
//排序
String[] sortedKeys = urlParams.getUrlParams().keySet().toArray(new String[]{});
Arrays.sort(sortedKeys);
// 生成stringToSign字符串
StringBuilder stringToSign = new StringBuilder();
stringToSign.append("GET").append(SEPARATOR);
stringToSign.append(percentEncode("/")).append(SEPARATOR);
StringBuilder canonicalizedQueryString = new StringBuilder();
for (String key : sortedKeys) {
//对key和value进行编码
canonicalizedQueryString.append(SEPARATOR).append(percentEncode(key)).append("=").append(percentEncode(urlParams.getUrlParams().get(key)));
}
//对canonicalizedQueryString进行编码
stringToSign.append(percentEncode(canonicalizedQueryString.toString().substring(1)));
//用stringToSign计算签名HMAC值
String key = SECRET + SEPARATOR;
Mac mac = Mac.getInstance(ALGORITHM);
mac.init(new SecretKeySpec(key.getBytes(ENCODING), ALGORITHM));
byte[] signData = mac.doFinal(stringToSign.toString().getBytes(ENCODING));
//按照Base64编码规则把上面的HMAC值编码成字符串
signature = Base64.encodeToString(signData, Base64.DEFAULT);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
return signature;
}
/**
* RFC3986编码规则:编码后的字符串中加号(+)替换成%20、星号(*)替换成%2A、%7E替换回波浪号(~)即可
* @param value
* @return
* @throws UnsupportedEncodingException
*/
private String percentEncode(String value) throws UnsupportedEncodingException {
return value != null ? URLEncoder.encode(value, "utf-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
}
/**
* 本地时间转UTC时间
* @param localTime
* @return
*/
public static String local2Utc(long localTime){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return simpleDateFormat.format(new Date(localTime));
}
/**
* 获取count个随机数
* @param count
* @return
*/
public static String getNewRandomStr(int count){
StringBuffer sb = new StringBuffer();
String str = "0123456789";
Random r = new Random();
for (int i = 0; i < count; i++) {
int num = r.nextInt(str.length());
sb.append(str.charAt(num));
str = str.replace((str.charAt(num) + ""), "");
}
return sb.toString();
}
说说坑,第一个坑就是getSignature(params)这里没有去空格,导致编码多出个空格的编码;
//签名:需要以上参数共同参与才能得出签名
params.put("Signature", getSignature(params).trim());
第二个坑就是对签名多进行了一次编码,文档中说
注意: 得到的签名值在作为最后的请求参数值提交给OPENSEARCH服务器的时候,要和其他参数一样,按照RFC3986的规则进行URL编码)
好吧,怪我自己没有注意到我们的网络请求框架已经对请求参数进行了编码,结果我自己又多编码了一次。