前言
主平台为对接放分配 appid和 secret,用于请求接口使用。 主平台接口采用HTTPS+JSON方式请求,输入和输出参数都采用UTF-8编码,并对所有接口均对调用方进行身份鉴权,以确认调用方合法性。
公共参数说明
签名
将appid、timestamp、nonce、secret(由主平台为第三方应用分配的密匙)的值进行字典排序,然后在用”&”符号进行连接四个值,再用UTF-8编码转为字节数组之后进行MD5加密,再转为大写的16进制字符串。请求携带参数appid和sign,只有拥有合法的身份appid和正确的签名sign才能放行。用以解决身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到secret(仅作本地加密使用,不参与网络传输),无法伪造合法的请求。
签名类
public class SendSignUtil {
/**
* @author leichunhong
* @description:组装签名
* @date 2019/2/18 10:43
* @Param
* @Return
*/
public static String createFrsUrl(String appId, String secret) {
String nonce = UUIDUtil.getUUID();
String timestamp = String.valueOf(System.currentTimeMillis());
List<String> list = new ArrayList<>();
list.add(appId);
list.add(timestamp);
list.add(secret);
list.add(nonce);
String sign = encrypt(addSymbol(list));
String suffix = "timestamp=" + timestamp + "&nonce=" + nonce + "&sign=" + sign + "&appId=" + appId;
return suffix;
}
/**
* @auther leichunhong
* @desc:
* @date 2020-06-03 15:01
* @param
* @return java.lang.String
*/
public static String addSymbol(List<String> list) {
StringBuilder content = new StringBuilder();
Collections.sort(list);
for (String element : list) {
content.append(element).append("&");
}
content.deleteCharAt(content.length() - 1);
return content.toString();
}
/**
* @param
* @return java.lang.String
* @auther leichunhong
* @desc md5转码
* @date 2020-05-25 16:32
*/
public static String encrypt(String str) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (Exception e) {
e.printStackTrace();
}
byte[] md5Bytes = md5.digest(str.getBytes());
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString().toUpperCase();
}
public static void main(String[] args) {
//在地址栏后拼接上参数
//原始访问地址:http://127.0.0.1:9080/860475033169189
//带签名访问:http://127.0.0.1:9080/860475033169189?timestamp=xxx&nonce=xxx&sign=xxx&appId=xxx
System.out.println("原始访问地址:http://127.0.0.1:9080/860475033169189");
System.out.println("带签名访问:http://127.0.0.1:9080/860475033169189?" + createFrsUrl("c3669f6829464aa2b51a7bc3e4ecec10", "fe4506a359024ea2b5e583a25586b84b"));
}
}
验签类
@Component
public class CheckHttpUtil {
@Autowired
private RedisService redisService;
final String NONCESTR_KEY = "nonce_key";
/***
* @Description: 验签
* @Param: [appId, sign, nonce, timestamp]
* @return: cn.thinkjoy.springboot.business.response.HttpResponse
* @Author: leichunhong
* @Date: 2021-03-02
*/
public HttpResponse check(String appId, String sign, String nonce, String timestamp) {
//此参数不通过网络传输 读者可以数据库保存或者 加入配置文件
String secret = "fe4506a359024ea2b5e583a25586b84b";
//判断时间
long now = System.currentTimeMillis();
long pathTimestamp = Long.parseLong(timestamp);
//十分钟之外超时 抛异常
long s = (now - pathTimestamp) / (1000 * 60);
if (s > 10 || s < 0) {
throw new PhoneBizException(201, "调用接口超时!");
}
//十分钟之内验证nonce重复
//判断nonce是否用过
Object obj = redisService.get(NONCESTR_KEY, nonce);
if (obj != null) {
throw new PhoneBizException(202, "此接口被调用过!已经被丢弃");
}
return testSign(secret, appId, sign, nonce, timestamp);
}
/**
* @author leichunhong
* @description
* @date 2019/2/18 10:43
* @Param
* @Return
*/
public HttpResponse testSign(String secret, String appId, String sign, String nonce, String timestamp) {
List<String> list = new ArrayList<>();
list.add(appId);
list.add(timestamp);
list.add(secret);
list.add(nonce);
String nowsign = SendSignUtil.encrypt(SendSignUtil.addSymbol(list));
if (nowsign.equals(sign)) {
//redis 存 nonce 值 保存10分钟
redisService.set(NONCESTR_KEY, nonce, nonce, 10 * 60L);
return ResultUtil.getHttpResponse(200, "验签验证成功!", null);
} else {
throw new PhoneBizException(203, "验签失败!");
}
}
}
说明:
1.接口调用如果和当前时间比较是十分钟之外的时间,直接抛出异常调用超时。
2.接口调用如果是十分钟之内验证nonce是否已经使用过,如果使用过证明有相同的接口被调用过,接口抛出异常,此接口被调用过!已经被丢弃。
3.上述两步验证成功之后,用传入的参数生成sign和接口中传过来的sign对比,如果相等验签成功,如果不相等验签失败。
4.ResultUtil为返回封装类,PhoneBizException为异常类,根据情况删除自行封装。