1.自定义的加签规则
1.采用Http协议的get或post方式提交
其中get请求参数放在Params中,post请求参数放在Params中,或者body中传递
2.增加接口请求时间戳参数:timestamp ,放到 Params,参与签名计算
如:timestamp=1665727846 ,(秒)
3.签名算法说明
1)获取params参数和body中的非空、非空串的参数
2)所有的参数按参数名升序排序
3)按参数名及参数值互相连接组成一个请求参数串(paramStr),格式如下:
name1=value1&name2=value2 ...
4)将secretKey(服务器密钥:4f6cxxxx38d892xxx),拼接到请求参数串的尾部,得到签名字符串(signStr)
paramStr+"&key="+secretKey
5)将signStr通过MD5加密,得到 签名(sign)
4. timestamp 、sign 放到 Params中,与其他接口请求参数一起发送给服务端
2.自定义的接口签名解签工具类
package com.camojojo.common.core.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.camojojo.common.core.constant.WebError;
import com.camojojo.common.core.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Flux;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* 接口签名验证
* @author:yuchen
* @createTime:2022/10/13 19:50
*/
@Slf4j
public class SignUtil {
private static final String secretKey = "key=xxxxxxxxx";
private static final String SIGN = "sign";
private static final String TIMESTAMP = "timestamp";
public static void checkSign(ServerHttpRequest request,long expireTime) {
log.info("签名验证");
String timeStr = request.getQueryParams().getFirst(TIMESTAMP);
String sign = request.getQueryParams().getFirst(SIGN);
log.info("sign={},timeStr={}",sign,timeStr);
if(StrUtil.isBlank(timeStr) || StrUtil.isBlank(sign)){
throw new ServiceException(WebError.ERROR_COMMON_PARAMETER_IS_EMPTY,"参数为空");
}
long requestTime = Long.valueOf(timeStr);
long now = System.currentTimeMillis()/1000;
log.info("now={}",now);
// 请求发起时间与当前时间超过expireTime,则接口请求过期
if(now - requestTime > expireTime){
throw new ServiceException(WebError.ERROR_API_REQUEST_EXPIRE,"接口请求过期");
}
Map<String, String> paramMap = new HashMap<>();
String body = resolveBodyFromRequest(request);
log.info("获取body中的参数={}",body);
MultiValueMap<String, String> queryParams = request.getQueryParams();
queryParams.forEach((k, v) -> {
if(v != null && StrUtil.isNotBlank(v.get(0)) && !k.equalsIgnoreCase(SIGN)){
paramMap.put(k,v.get(0));
}
});
log.info("获取params中的参数={}",paramMap);
if(StrUtil.isNotBlank(body)){
JSONObject jsonObject = JSON.parseObject(body);
for(String key :jsonObject.keySet()){
if(StrUtil.isNotBlank(jsonObject.getString(key))){
paramMap.put(key,jsonObject.getString(key));
}
}
}
String serverSign = getSign(paramMap);
log.info("serverSign={}",serverSign);
if(!serverSign.equals(sign)){
throw new ServiceException(WebError.ERROR_API_SIGN,"接口签名错误");
}
}
public static String getSign(Map<String, String> map) {
//按Key进行排序
Map<String, String> sortMap = sortMapByKey(map);
StringBuilder signStr= new StringBuilder();
if(sortMap != null){
for (Map.Entry<String, String> entry : sortMap.entrySet()) {
signStr.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
String sign = signStr.append(secretKey).toString();
log.info("signStr={}",signStr);
return SecureUtil.md5(sign);
}
/**
* 使用 Map按key进行排序
* @param map
* @return
*/
public static Map<String, String> sortMapByKey(Map<String, String> map) {
if (map == null || map.isEmpty()) {
return null;
}
//升序排序
Map<String, String> sortMap = new TreeMap<>(String::compareTo);
sortMap.putAll(map);
return sortMap;
}
private static String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
}
3.进一步优化
在实际实现中,发现body中参数可能包含数组,或者对象数组,此时按照以上需求也需要对每个key进行排序,否则会由于顺序原因造成前后端生成的签名不一致。
后续优化,将body转成流,再进行md5加密,生成签名。这样就避免了对body处理繁琐的加签操作。
本文转载自: https://blog.csdn.net/qq_38971617/article/details/128083610
版权归原作者 程序员屿辰 所有, 如有侵权,请联系我们删除。
版权归原作者 程序员屿辰 所有, 如有侵权,请联系我们删除。