目錄
1、使用場景
最近因爲公司要求與其他部門對接用戶中心的要求;在對接過程之中。對方提供的以php實現的後端用戶接口,以Rest風格提供。並且因爲安全的要求,需要對請求的參數進行前端加密驗證。具體有如下規則:
驗籤規則以下5點
- 輸入參數按key名升序排序 示例入參 name=張三,sex=1,birth=19901211
- 進行urlquery類型格式化 birth=19901211&name=張三&sex=1 (參數值進行urlencode)如參數值類型是數組,先jsonencode,在urlencode
- 所得拼接參數 str = birth=19901211&name=張三&sex=1&access_key=ak
- 將所得字符串md5後進行sha256加密 hmac-sha256(md5(str),secret)
- ak=頒發的ak,secret =頒發的sk
2、實現過程幾點思考
2.1、上加密規則已經提出了Get/Post如何加密
上面加密規則,提出如何針對普通的get請求而參數進行基本的參數請求驗證規則,其中針對比較複雜的數組形也使用說明了加密方式。已經最後使用MD5和Sha256相關加密模式生成相關的驗證前面sigin。
2.2、針對其中一條 參數按key名升序排序
可能不同人會想到有一下兩種方法進行實現:
以下是我在相關的項目之中見到的兩種實現方式。但是個人比較贊同和佩服使用TreeMap方式比較簡潔明瞭。
- 使用Collection.sort進行排序實現
/**
* 給參數排序,根據參數的名稱而不是根據參數值,以便生成一致的加密源字符串
* @param params
* @return
*/
private static String sort(Map<String, String[]> params) {
if (params == null || params.size() < 1) {
return "";
}
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
int size = keys.size();
StringBuilder prestr = new StringBuilder();
for (int i = 0; i < size; i++) {
String key = (String) keys.get(i);
String[] values = params.get(key);
Arrays.sort(values);
//拼接時,不包括最後一個&字符
if (i == size - 1) {
prestr.append(key).append("=").append(values[0]);
} else {
prestr.append(key).append("=").append(values[0]).append("&");
}
}
return prestr.toString();
}
- 使用TreeMap的自帶排序功能實現
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
/**
* @Classname RequestParamsSignatureHUtil
* @Description 請求參數前面工具
* @Date 2019/11/20 17:05
* @Created by jianxiapc
*/
public class RequestParamsCryptUtils {
private static Logger logger = LoggerFactory.getLogger(WdyEduRequestParamsCryptUtils.class);
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
public static String getSha256Md5Sign(Map<String, Object> params, String access_key, String salt) {
Map<String, Object> sortMap = new TreeMap<String, Object>();
sortMap.putAll(params);
StringBuffer sb = new StringBuffer();
for (String key : sortMap.keySet()) {
Object obj = params.get(key);
if (ObjectUtils.isArray(obj) || obj instanceof ArrayList) {
logger.info("進入處理 數組或者List 參數加密");
sb.append("&" + key + "="
+ encodeToUrlSafeString(StringUtils.trimWhitespace(JSON.toJSONString(params.get(key)))));
} else {
logger.info("進入字符串處理模式 "+ obj.getClass().getName() );
// 進入處理字符串內容 此處需要針對jsonArray進行特殊處理
if(obj instanceof JSONArray){
JSONArray dataJsonArray=(JSONArray)obj;
logger.info("進入dataJsonArray 處理模式 "+ dataJsonArray.toString() );
sb.append("&" + key + "="
+ encodeToUrlSafeString(StringUtils.trimWhitespace(dataJsonArray.toString())));
}else{
sb.append("&" + key + "="
+ encodeToUrlSafeString(StringUtils.trimWhitespace(params.get(key)+"")));
}
}
}
sb.append("&access_key=" + access_key);
return sha256(md5(sb.toString().substring(1)), salt);
}
public static String sha256(String rawStr, String salt) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(salt.getBytes("utf-8"), "HmacSHA256");
mac.init(secret_key);
byte[] bytes = mac.doFinal(rawStr.getBytes("utf-8"));
return toHex(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String md5(String rawStr) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(rawStr.getBytes("utf-8"));
return toHex(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String toHex(byte[] bytes) {
StringBuilder ret = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
ret.append(HEX_DIGITS[b >> 4 & 0x0f]);
ret.append(HEX_DIGITS[b & 0x0f]);
}
return ret.toString();
}
@SuppressWarnings("deprecation")
public static String encodeToUrlSafeString(String value) {
return URLEncoder.encode(value);
}
}
3、成果展現
這個請求參數加密規則部分;在前後端交互部分起到比較重要的作用,因爲只有正確的sigin認證才能夠在後端才能通過驗證;纔可以有訪問相關接口;並且返回接口的相關資源信息。此文參考了兩個項目的實現方式。後續再有相關文章會繼續完善此部分內容
4、總結
書寫此文的目的是記錄自己在實際項目之中遇見的基本請求參數加密認證模式的實現方式。也是在以後相關項目後能夠使用到的時候能夠作爲一個資料進行查閱。