模拟微信支付通过appid、appsecret、key使用md5获取,实现接口验签

不打扰是莪最后的温柔 提交于 2020-03-07 02:40:06

使用场景,在接口开发过程中,我们通常不能暴露一个接口给第三方随便调用,要对第三方发来参数进行校验,看是不是具有访问权限,在微信支付接口中也是这个道理,我们要开通微信支付,微信会提供给我们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等配置。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!