使用场景,在接口开发过程中,我们通常不能暴露一个接口给第三方随便调用,要对第三方发来参数进行校验,看是不是具有访问权限,在微信支付接口中也是这个道理,我们要开通微信支付,微信会提供给我们appid(公众账号ID)、mer_id(商户号),appsecret(密钥),然后通过字段拼接,获取签名,发送给微信,微信验证没有问题才会返回正确数据。
注意:MD5验签有两个作用
1. 保证数据在传输过程中不会丢失
2. 通过分配appid、appsecret保证签名只有授权用户可以访问通过
进入正题
第一步. MD5根据appid、appsecret、时间戳生成签名
首先分配参数appid、appsecret
appid自定义,appsecret通过uuid获取
appid:用户标识,每个用户有不同得appid
appsecret: 安全密钥,必须事先分配给接口提供方用于验签
第二步. 根据用户发来数据验签
直接上代码,签名验证公共类
package com.lf.md5.util;
import lombok.extern.slf4j.Slf4j;
import java.security.MessageDigest;
import java.util.*;
/**
* @Title:
* @Package
* @Description: 生成有序map,签名,验签
* @author insistOn
* @date 2020/3/422:03
*/
@Slf4j
public class MD5 {
/**
* 生成微信支付sign
* @return
*/
public static String createSign(SortedMap<String, String> params, String key){
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String,String>> it = es.iterator();
//生成 stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
while (it.hasNext()){
Map.Entry<String,String> entry = (Map.Entry<String,String>)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k+"="+v+"&");
}
}
sb.append("key=").append(key);
String sign = MD5(sb.toString()).toUpperCase();
return sign;
}
/**
* 校验签名
* @param params
* @param key
* @return
*/
public static boolean isCorrectSign(SortedMap<String, String> params, String key){
String sign = createSign(params,key);
String weixinPaySign = params.get("sign").toUpperCase();
log.info("通过用户发送数据获取新签名:{}", sign);
return weixinPaySign.equals(sign);
}
/**
* 获取有序map
* @param map
* @return
*/
public static SortedMap<String,String> getSortedMap(Map<String,String> map){
SortedMap<String, String> sortedMap = new TreeMap<>();
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()){
String key = (String)it.next();
String value = map.get(key);
String temp = "";
if( null != value){
temp = value.trim();
}
sortedMap.put(key,temp);
}
return sortedMap;
}
/**
* md5常用工具类
* @param data
* @return
*/
public static String MD5(String data){
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte [] array = md5.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
获取签名测试类
package com.lf.md5.util;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.SortedMap;
import java.util.TreeMap;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author insistOn
* @Title: 验签测试
* @Package
* @Description: 验签作用:
* 1. 防注数据传输中被串改
* 2. 指定appid、key、appsecret的用户才能访问接口
* @date 2020/3/422:08
*/
@Slf4j
class MD5Test {
/**
* 实际项目使用需根据自己需要,提前分配appid和appsecret
*/
// 随便定义一个值
private static final String appid = "wx123456789";
// 提前生成一个appsecret用于验签,CommonUtils.generateUUID()
private static final String appsecret = "7214fefff0cf47d7950cb2fc3b5d670a";
// 定义时间戳, 时间戳作用是防止接口被重复调用,真正使用时,可以验证当时间在20秒内可以访问,其他时间超时
private static final String time = "1583332804914";
// 把数据按照 首字母排序
public static SortedMap<String, String> getData(){
SortedMap<String, String> sortedMap = new TreeMap<>();
sortedMap.put("str1", "某管理系统");
sortedMap.put("appid", appid);
sortedMap.put("timestamp", time);
return sortedMap;
}
@Test
void createSign() {
// 模拟发送端根据appid等数据生成签名
log.info("用户发宋签名:{}", MD5.createSign(getData(), appsecret));
}
}
执行测试类,打印出签名为:
19:52:13.330 [main] INFO com.lf.md5.util.MD5Test - 用户发送签名:6646BFE4E9DD66FCC096B115230FA577
controller代码如下
package com.lf.md5.controller;
import com.lf.md5.util.MD5;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author insistOn
* @Title:
* @Package
* @Description:
* @date 2020/3/618:54
*/
@RestController
public class SignController {
private static final String appsecret = "7214fefff0cf47d7950cb2fc3b5d670a";
/**
* 模拟验签
* @param appid
* @param timestamp
* @param str1 你要穿的参数, 还有其他参数可继续加
* @param sign 签名
* @return
*/
@RequestMapping("sign")
public String checkSign(@RequestParam String appid,
@RequestParam String timestamp,
@RequestParam String str1,
@RequestParam String sign){
// 1. 校验时间戳
long t = Long.valueOf(timestamp);
// 时间查过20秒,则认为接口为重复调用,返回错误信息
Date date = new Date();
long nowtime = date.getTime();
int seconds = (int) ((nowtime - t)/1000);
// if(seconds > 20)
// return "接口不允许重复访问!";
// 2. 组装参数
SortedMap<String, String> sortedMap = new TreeMap<>();
sortedMap.put("str1", str1);
sortedMap.put("appid", appid);
sortedMap.put("timestamp", timestamp);
sortedMap.put("sign", sign);
// 3. 校验签名
Boolean flag = MD5.isCorrectSign(sortedMap, appsecret);
return flag? "签名验证通过" : "签名验证未通过";
}
}
注释掉验证时间戳代码,调用验签接口,postman输入以下链接,查看返回信息:
开启时间戳代码,测试时间大于20秒,则不允许访问,返回值如下
结尾:有兴趣的化,可以把appid等信息放数据库,对字段进行单个校验,同时支持多个appid等配置。
来源:CSDN
作者:it1993
链接:https://blog.csdn.net/it1993/article/details/104682832