目录
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、总结
书写此文的目的是记录自己在实际项目之中遇见的基本请求参数加密认证模式的实现方式。也是在以后相关项目后能够使用到的时候能够作为一个资料进行查阅。