您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

Springboot使用OkHttp实现微信支付API-V3签名、证书的管理和使用

时间:2021-12-29 11:42:49  来源:  作者:666Tec

微信支付API-V3和V2的区别

微信支付API-V3和之前V2版本最大的区别,应该就是加密方式的改变了。新版的支付接口,全部使用是SSL双向加密。就是指微信服务器端、商户端各自都有一套证书,两者之间通讯必需使用自己证书的私钥加密,使用对方的公钥解密。具体流程图,可以参考微信支付官网的这张图:

Springboot使用OkHttp实现微信支付API-V3签名、证书的管理和使用

 

上图所在的文档链接是:
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml 有需要的可参考。

由于微信支付官方提供的JAVA Demo使用的是Httpclient,并且比较庞杂。所以我自己对接的时候,是使用OkHttp完成的。

接入前准备

每种支付方式的准备都稍有区别,但区别也不大。这里以JS-API支付为例,简单说说一个全新的微信支付账号,要做哪些配置。

  1. 绑定App ID和商户号mch id:微信支付申请下来后,是没办法单独使用的。肯定要依托于公众号、小程序、APP或者网站等载体,这些载体都有自己的APP ID,需要在这些载体对应的后台里,找到微信支付菜单,点进去把微信支付的mch id和app id绑定起来。
  2. 设置API KEY:这个key主要用来解密一些微信接口的返回结果,比如下载微信平台证书的时候。(为什么在双向证书的情况下,还需要这个key呢?因为这些证书,只使用来签名的,并不能加密每次请求的body)
  3. 下载商户证书:这个没啥好说的,新版的微信支付V3接口,商户和微信平台,各自都有证书。
  4. 配置各种授权域名、授权目录等。

以上就是接入前准备的简单介绍,具体每一步的详细操作,可以参考微信支付官方的文档:

  • JSAPI接入前准备:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml
  • APP支付接入前准备https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml
  • 小程序支付接入前准备https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml

使用OkHttp封装自带微信支付API-V3证书加解密、签名的请求

微信支付本身对接起来不麻烦,无非就是下单、支付、等通知该状态三步。对新手不友好的地方,主要还是各种加密、签名等安全措施。接下来我介绍一下如何使用OkHttp封装一个http请求类,包含各种安全验证措施,外部调用的时候,只要当普通OkHttp接口调用就行。各种权限验证,已经在类里面自己实现了。

简单介绍一下封装类的各个方法:

  • generateToken :当我们请求微信的接口的时候,首先得生成签名信息,放到HTTP请求的Header里,名字叫Authorization。
  • checkResponseSign :拿到微信的返回结果后,我们得拿返回结果算一下签名,然后和返回的签名对比一下,看看这个请求结果是真的还是伪造的。
  • decodeWxPlatCert :微信的平台证书,定期会自动更新,我们需要调用接口下载微信的平台证书回来。这个接口的返回结果,是用我们前面“接入前准备”中提到的API KEY加密的,所以,我们得用这个方法解密。
  • wxGet wxPost :这两个就是封装好的HTTP GET和HTTP POST请求了,已经在内部实现了各种安全措施。

完整代码如下,代码依赖了很多常见的类库,比如Apache-commons-lang3等,可以从代码的import中看出来,自行添加maven pom。

package com.coderbbb.blogv2.utils;

import com.coderbbb.blogv2.database.dos.WxPlatCertDO;
import com.coderbbb.blogv2.database.dto.WxCertDataDTO;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class WxOkHttpUtil extends OkhttpUtil {

    private static final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"";

    private static final String CERT_LIST = "https://api.mch.weixin.qq.com/v3/certificates";

    public static ConcurrentHashMap<String, WxPlatCertDO> WX_PLAT_CERT = new ConcurrentHashMap<>();

    private final static Logger logger = LoggerFactory.getLogger(WxOkHttpUtil.class);

    /**
     * 生成请求微信接口时需要的Authorization头
     * @param url
     * @param method
     * @param json
     * @return
     */
    public static String generateToken(String url, String method, String json) {
        if (json == null) {
            json = "";
        }
        url = url.substring(StringUtils.ordinalIndexOf(url, "/", 3));

        long timestamp = System.currentTimeMillis() / 1000;
        String timestampStr = String.valueOf(timestamp);
        String nonceStr = RandomStringUtils.random(16, true, true);

        String signatureStr = Stream.of(method.toUpperCase(Locale.ROOT), url, timestampStr, nonceStr, json).collect(Collectors.joining("n", "", "n"));

        WxCertDataDTO wxCertDataDTO = WxCertUtil.getCert();


        String signResult;
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(wxCertDataDTO.getPrivateKey());
            sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
            signResult = Base64.encodeBase64String(sign.sign());
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }


        //开始拼接Token
        return String.format(TOKEN_PATTERN, wxCertDataDTO.getMchId(), nonceStr, timestamp, wxCertDataDTO.getSerialNumber(), signResult);
    }

    /**
     * 请求基本Header头,微信规定的。
     * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay2_0.shtml
     * @param headerMap
     * @return
     */
    private static HashMap<String, String> intHeader(HashMap<String, String> headerMap) {
        if (headerMap == null) {
            headerMap = new HashMap<>();
        }
        headerMap.put("content-type", "application/json;charset=UTF-8");
        headerMap.put("user-agent", "coderbbb");
        headerMap.put("accept", "application/json");
        return headerMap;
    }

    /**
     * 微信请求我们时(比如支付的异步通知),拿这个函数校验微信的请求是否是合法的
     * 简单说,就是验证微信请求我们时,签名是否正确的。
     * @param request
     * @param requestBody
     * @return
     */
    public static boolean checkServletRequestSign(HttpServletRequest request, String requestBody) {

        String wxCertSerialNumber = request.getHeader("Wechatpay-Serial");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String sign = request.getHeader("Wechatpay-Signature");

        if (!WX_PLAT_CERT.containsKey(wxCertSerialNumber)) {
            return false;
        }

        WxPlatCertDO wxPlatCertDO = WX_PLAT_CERT.get(wxCertSerialNumber);

        String signBody = Stream.of(timestamp, nonce, requestBody).collect(Collectors.joining("n", "", "n"));

        //开始验签
        try {
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(wxPlatCertDO.getCertificate());
            signature.update(signBody.getBytes(StandardCharsets.UTF_8));
            if (!signature.verify(Base64.decodeBase64(sign))) {
                return false;
            }
        } catch (Exception e) {
            logger.error("验签错误", e);
            return false;
        }
        return true;
    }

    /**
     * 我们请求微信的接口后,得到返回结果,用这个函数把返回结果生成签名,和返回的签名对比是否一致
     * 简单说,就是请求微信的接口后,我们自己用返回结果生成一个签名,和请求返回的签名对比,看看签名一样不
     * @param response
     * @param lazyVerify
     * @return
     */
    private static String checkResponseSign(Response response, boolean lazyVerify) {
        String result = null;
        String wxCertSerialNumber;
        String timestamp;
        String nonce;
        String sign;

        try (ResponseBody body = response.body();) {
            if (body != null) {
                result = body.string();
            }

            if (response.code() != 200) {
                logger.warn("err response = " + result);
                throw new RuntimeException("请求http code异常:" + response.code());
            }

            wxCertSerialNumber = response.header("Wechatpay-Serial");
            timestamp = response.header("Wechatpay-Timestamp");
            nonce = response.header("Wechatpay-Nonce");
            sign = response.header("Wechatpay-Signature");
        } catch (IOException e) {
            throw new RuntimeException("wx okHttp read response err", e);
        }

        WxPlatCertDO wxPlatCertDO = null;
        if (!WX_PLAT_CERT.containsKey(wxCertSerialNumber)) {
            //平台证书不在已有的列表内
            /**
             * 我们提供以下的机制,帮助商户在平台证书更新时实现平滑切换:
             *
             * 1.下载新平台证书。我们将在旧证书过期前10天生成新证书。
             * 商户可使用平台证书下载API 下载新平台证书,并在旧证书过期前5-10天部署新证书。
             *
             * 2.兼容使用新旧平台证书。旧证书过期前5天至过期当天,新证书开始逐步放量用于应答和回调的签名。
             * 商户需根据证书序列号,使用对应版本的平台证书。
             * (我们在所有API应答和回调的HTTP头部Wechatpay-Serial,声明了此次签名所对应的平台证书的序列号。)
             */
            //所以:定时任务拉取微信平台证书,在这里,如果证书不在列表内,只有两种情况:
            //1.该请求是第一次下载微信平台证书的请求;2.恶意请求。
            // - 我们使用lazyVerify标记第一种情况
            if (!lazyVerify) {
                //不能延迟验签,抛出错误
                throw new RuntimeException("签名校验失败");
            } else {
                //可以延迟验签,说明该请求是下载微信平台证书的请求,直接读取请求返回值,提取证书
                List<WxPlatCertDO> wxPlatCertData = decodeWxPlatCert(result);
                for (WxPlatCertDO item : wxPlatCertData) {
                    if (item.getSerialNumber().equals(wxCertSerialNumber)) {
                        wxPlatCertDO = item;
                    }
                }
            }
        } else {
            wxPlatCertDO = WX_PLAT_CERT.get(wxCertSerialNumber);
        }

        if (wxPlatCertDO == null) {
            throw new RuntimeException("平台证书不存在,验签失败");
        }

        String signBody = Stream.of(timestamp, nonce, result).collect(Collectors.joining("n", "", "n"));

        //开始验签
        try {
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(wxPlatCertDO.getCertificate());
            signature.update(signBody.getBytes(StandardCharsets.UTF_8));
            if (!signature.verify(Base64.decodeBase64(sign))) {
                throw new RuntimeException("签名错误,请求不安全");
            }
        } catch (Exception e) {
            throw new RuntimeException("验签错误", e);
        }

        return result;
    }

    /**
     * 下载微信平台证书时,用这个函数解密拿到的返回值,得到微信平台证书列表
     * @param json
     * @return
     */
    private static List<WxPlatCertDO> decodeWxPlatCert(String json) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode;
        JsonNode dataNode;
        try {
            jsonNode = mapper.readTree(json);
            dataNode = jsonNode.get("data");
        } catch (Exception e) {
            throw new RuntimeException("读取证书JSON失败", e);
        }

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

        List<WxPlatCertDO> certList = new ArrayList<>();
        for (JsonNode itemNode : dataNode) {
            JsonNode certNode = itemNode.get("encrypt_certificate");
            String cert = AesUtil.decryptJsonNodeToString(certNode);

            try {
                CertificateFactory cf = CertificateFactory.getInstance("X509");
                X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(
                        new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8))
                );
                x509Cert.checkValidity();

                WxPlatCertDO wxPlatCertDO = new WxPlatCertDO();
                wxPlatCertDO.setCertificate(x509Cert);
                wxPlatCertDO.setCert(cert);
                wxPlatCertDO.setEffectiveTime(format.parse(itemNode.get("effective_time").asText()));
                wxPlatCertDO.setExpireTime(format.parse(itemNode.get("expire_time").asText()));
                wxPlatCertDO.setSerialNumber(itemNode.get("serial_no").asText());

                certList.add(wxPlatCertDO);
            } catch (Exception e) {
                logger.error("update wx plat cert err", e);
            }
        }
        return certList;
    }

    /**
     * 封装的http get请求,直接使用即可,证书、签名的生成和校验已经集成
     * @param url
     * @param headerMap
     * @return
     */
    public static String wxGet(String url, HashMap<String, String> headerMap) {

        boolean lazyVerify = false;
        if (url.equals(CERT_LIST)) {
            //是下载证书的请求,允许延迟验签
            lazyVerify = true;
        }

        String token = generateToken(url, "get", null);

        headerMap = intHeader(headerMap);
        headerMap.put("Authorization", token);


        Request.Builder builder = new Request.Builder().url(url);

        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }

        Request request = builder.build();
        try (Response response = getClient().newCall(request).execute()) {
            return checkResponseSign(response, lazyVerify);
        } catch (IOException e) {
            throw new RuntimeException("wx okHttp get err", e);
        }
    }

    /**
     * 封装的http post请求,直接使用即可,证书、签名的生成和校验已经集成
     * @param url
     * @param json
     * @param headerMap
     * @return
     */
    public static String wxPost(String url, String json, HashMap<String, String> headerMap) {
        RequestBody requestBody = RequestBody.create(json, MediaType.parse("application/json"));

        String token = generateToken(url, "post", json);

        headerMap = intHeader(headerMap);
        headerMap.put("Authorization", token);

        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }
        try (Response response = getClient().newCall(builder.build()).execute()) {
            return checkResponseSign(response, false);
        } catch (IOException e) {
            throw new RuntimeException("wx okHttp post err", e);
        }
    }

    /**
     * 从微信下载微信平台证书
     * @return
     */
    public static List<WxPlatCertDO> getCertList() {
        String s = wxGet(CERT_LIST, null);
        return decodeWxPlatCert(s);
    }

}

上面的代码中,依赖了很多我自己写的其他类库,这里逐一介绍一下。

  • OkHttpUtil 基于OkHttp封装的HTTP请求库,代码后面放。
  • AesUtil 敏感信息解密的类,比如微信下载平台证书的请求,返回结果就是加密的,需要解密。
  • WxCertUtil 主要有两个功能,一个是从本地文件读取商户自己的证书,给其他地方用;另一个是请求微信时,对敏感信息加密,这个功能和AesUtil是对应的。
  • 各种POJO类,比如加载证书信息后,证书信息有一个POJO类。这些类就不放代码了,每个人需求不一样,自己自定义即可。 好吧,好多读者反馈需要,那我放到文章最后。
package com.coderbbb.blogv2.utils;

import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


public class OkhttpUtil {

    private static OkHttpClient client = null;

    private static final Logger logger = LoggerFactory.getLogger(OkhttpUtil.class);

    private synchronized static void createClient() {
        if (client == null) {
            OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();

            okHttpBuilder.protocols(Collections.singletonList(Protocol.HTTP_1_1));
            okHttpBuilder.connectTimeout(60, TimeUnit.SECONDS);
            okHttpBuilder.readTimeout(60, TimeUnit.SECONDS);
            okHttpBuilder.writeTimeout(60, TimeUnit.SECONDS);
            okHttpBuilder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    //支持所有类型https请求
                    return true;
                }
            });

            ConnectionPool pool = new ConnectionPool(200, 1, TimeUnit.SECONDS);
            okHttpBuilder.connectionPool(pool);

            client = okHttpBuilder.build();
            client.dispatcher().setMaxRequests(2000);
            client.dispatcher().setMaxRequestsPerHost(1000);
        }
    }

    public static OkHttpClient getClient() {
        if (client == null) {
            createClient();
        }
        return client;
    }

    public static String get(String url, HashMap<String, String> headerMap) {
        Request.Builder builder = new Request.Builder().url(url);

        if (headerMap != null) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
        }

        Request request = builder.build();

        String result = null;
        try {
            result = excute(request);
        } catch (Exception e) {
            logger.warn("http get fail:" + url + "###" + e.getMessage());
        }
        return result;
    }

    public static String postRaw(String url, String contentType, String json, HashMap<String, String> headerMap) throws Exception {
        RequestBody requestBody = RequestBody.create(json,MediaType.parse(contentType));

        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        if (headerMap != null) {
            for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
        }
        Request request = builder.build();
        String result = null;
        try {
            result = excute(request);
        } catch (Exception e) {
            logger.error("http post raw fail:" + url + "###" + e.getMessage());
        }
        return result;
    }

    public static String post(String url, HashMap<String, String> data, HashMap<String, String> headerMap) throws Exception {

        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        for (Map.Entry<String, String> entry : data.entrySet()) {
            formBodyBuilder.add(entry.getKey(), entry.getValue());
        }
        RequestBody requestBody = formBodyBuilder.build();

        Request.Builder builder = new Request.Builder().url(url).post(requestBody);
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }
        Request request = builder.build();
        String result = null;
        try {
            result = excute(request);
        } catch (Exception e) {
            logger.error("http post fail:" + url + "###" + e.getMessage());
        }
        return result;
    }

    private static String excute(Request request) throws Exception {
        Response response = getClient().newCall(request).execute();
        ResponseBody body = response.body();
        if (body != null) {
            String str = body.string();
            body.close();
            response.close();
            return str;
        }
        return null;
    }
}

package com.coderbbb.blogv2.utils;

import com.fasterxml.jackson.databind.JsonNode;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesUtil {


    private static final int TAG_LENGTH_BIT = 128;

    public static String aesKey;

    public static String decryptJsonNodeToString(JsonNode jsonNode){
        return decryptToString(
                jsonNode.get("associated_data").asText().getBytes(StandardCharsets.UTF_8),
                jsonNode.get("nonce").asText().getBytes(StandardCharsets.UTF_8),
                jsonNode.get("ciphertext").asText());
    }

    public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) {
        if (aesKey == null) {
            throw new RuntimeException("aesKey不能为空");
        }
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(aesKey.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);

            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("aes解密失败", e);
        }
    }
}

package com.coderbbb.blogv2.utils;

import com.coderbbb.blogv2.database.dto.WxCertDataDTO;
import org.apache.commons.codec.binary.Base64;
import org.springframework.core.io.ClassPathResource;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

/**
 * 微信支付证书解析
 *
 * @author longge93
 */
public class WxCertUtil {

    private static WxCertDataDTO wxCertDataDTO = null;

    public static String keyPass = null;

    public static String sslPath = "ssl/wx2.p12";

    private static synchronized WxCertDataDTO loadCert() {
        if (wxCertDataDTO != null) {
            return wxCertDataDTO;
        }

        if (keyPass == null) {
            throw new RuntimeException("还没有设置证书密码");
        }

        ClassPathResource classPathResource = new ClassPathResource(sslPath);

        String serialNumber;
        PublicKey publicKey;
        PrivateKey privateKey;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(classPathResource.getInputStream(), keyPass.toCharArray());

            Enumeration<String> aliases = keyStore.aliases();
            X509Certificate cert = (X509Certificate) keyStore.getCertificate(aliases.nextElement());

            //证书序列号
            serialNumber = cert.getSerialNumber().toString(16).toUpperCase();
            // 证书公钥
            publicKey = cert.getPublicKey();
            // 证书私钥
            privateKey = (PrivateKey) keyStore.getKey(keyStore.getCertificateAlias(cert), keyPass.toCharArray());
        } catch (Exception e) {
            throw new RuntimeException("读取证书失败", e);
        }

        wxCertDataDTO = new WxCertDataDTO();
        wxCertDataDTO.setPublicKey(publicKey);
        wxCertDataDTO.setPrivateKey(privateKey);
        wxCertDataDTO.setSerialNumber(serialNumber);
        wxCertDataDTO.setMchId(keyPass);

        return wxCertDataDTO;
    }

    public static WxCertDataDTO getCert() {
        if (wxCertDataDTO != null) {
            return wxCertDataDTO;
        }
        return loadCert();
    }

    public static String rsaSign(String signatureStr){

        WxCertDataDTO wxCertDataDTO = getCert();

        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(wxCertDataDTO.getPrivateKey());
            sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
            return Base64.encodeBase64String(sign.sign());
        } catch (Exception e) {
            throw new RuntimeException("RSA签名失败");
        }
    }
}

源代码使用方法

  • 首先,你得pom引入OkHttp和apache-commons-lang3(可能有遗漏,你如果有报错,就自己加一下)
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
  • 然后,把上面依赖的OkHttpUtil、AesUtil、WxCertUtil和WxOkHttpUtil放到一起,把报错的import改一改(因为你和我的包名不一样,肯定会报错)
  • 关于证书管理:我的策略是,每次程序启动(或定时任务),都会调用微信的接口,下载微信平台证书,保存到数据库,再保存到静态变量中WxOkHttpUtil类的WX_PLAT_CERT变量。(所以,你使用代码时,应该先调用下载WxOkHttpUtil中下载证书的接口,把证书加载到这个变量中,然后才能正常执行微信下单、退款等等操作)
  • WxCertUtil.keyPass是证书的密码,其实就是微信商户号
  • AesUtil.aesKey 就是前面接入准备中提到的API KEY

其他

到这一步,微信支付API-V3的各种安全校验问题应该是解决了,专栏下一篇就介绍怎么使用本文封装好的HTTP GET、HTTP POST来完成整个微信支付流程。

读者要求的POJO类代码

  • WxCertDataDTO
package com.coderbbb.book1.database.dto;

import lombok.Data;

import java.security.PrivateKey;
import java.security.PublicKey;

@Data
public class WxCertDataDTO {

    /**
     * 证书序列号
     */
    private String serialNumber;

    /**
     * 证书公钥
     */
    private PublicKey publicKey;

    /**
     * 证书私钥
     */
    private PrivateKey privateKey;

    /**
     * 商户号
     */
    private String mchId;
}
  • WxPlatCertDO,这个就是把证书往数据库存的实体类,继承了一个MybatisBaseDO类(这个类你可以删掉不继承,里面是我封装的数据库主键、时间戳等等通用字段,你按自己的喜好搞)。
package com.coderbbb.blogv2.database.dos;

import lombok.Data;

import java.security.cert.X509Certificate;
import java.util.Date;

@Data
public class WxPlatCertDO extends MybatisBaseDO{

    /**
     * 证书序列号
     */
    private String serialNumber;

    /**
     * 证书过期时间
     */
    private Date expireTime;

    /**
     * 生效时间
     */
    private Date effectiveTime;

    /**
     * X509Certificate JSON序列化
     */
    private String cert;

    /**
     * 证书
     */
    private X509Certificate certificate;
}

版权声明:《Java Springboot使用OkHttp实现微信支付API-V3签名、证书的管理和使用》为CoderBBB作者「ʘᴗʘ」的原创文章,转载请附上原文出处链接及本声明。

原文链接:
https://www.coderbbb.com/articles/4



Tags:Springboot   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
微信支付API-V3和V2的区别微信支付API-V3和之前V2版本最大的区别,应该就是加密方式的改变了。新版的支付接口,全部使用是SSL双向加密。就是指微信服务器端、商户端各自都有一...【详细内容】
2021-12-29  Tags: Springboot  点击:(0)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  Tags: Springboot  点击:(21)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  Tags: Springboot  点击:(25)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  Tags: Springboot  点击:(57)  评论:(0)  加入收藏
1. 介绍1.1 介绍今天开始我们来学习Java操作MySQL数据库的技巧,Java操作MySQL是借助JdbcTemplate这个对象来实现的。JdbcTemplate是一个多数据库集中解决方案,而我们今天只讲...【详细内容】
2021-11-05  Tags: Springboot  点击:(30)  评论:(0)  加入收藏
SpringBoot中的Controller注册本篇将会以Servlet为切入点,通过源码来看web容器中的Controller是如何注册到HandlerMapping中。请求来了之后,web容器是如何根据请求路径找到对...【详细内容】
2021-11-04  Tags: Springboot  点击:(54)  评论:(0)  加入收藏
环境:Springboot2.4.11环境配置接下来的演示都是基于如下接口进行。@RestController@RequestMapping("/exceptions")public class ExceptionsController { @GetMapping(...【详细内容】
2021-10-11  Tags: Springboot  点击:(42)  评论:(0)  加入收藏
SpringBoot项目默认使用logback, 已经内置了 logback 的相关jar包,会从resource包下查找logback.xml, logback 文件格式范本 可直接复制使用,有控制台 info.log error.log三个...【详细内容】
2021-10-09  Tags: Springboot  点击:(50)  评论:(0)  加入收藏
环境:Springboot2.4.10当应用程序启动时,Spring Boot将自动从以下位置查找并加载application.properties和application.yaml文件: 从Classpath类路径classpath的根类路径classp...【详细内容】
2021-09-26  Tags: Springboot  点击:(78)  评论:(0)  加入收藏
搭建基础1. Intellij IDEA 2. jdk1.8 3. maven3.6.3搭建方式(1)在线创建项目Spring Boot 官方提供的一种创建方式,在浏览器中访问如下网址: https://start.spring.io/在打开的页...【详细内容】
2021-09-14  Tags: Springboot  点击:(79)  评论:(0)  加入收藏
▌简易百科推荐
微信支付API-V3和V2的区别微信支付API-V3和之前V2版本最大的区别,应该就是加密方式的改变了。新版的支付接口,全部使用是SSL双向加密。就是指微信服务器端、商户端各自都有一...【详细内容】
2021-12-29  666Tec    Tags:Springboot   点击:(0)  评论:(0)  加入收藏
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(7)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(13)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(15)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(19)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(27)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条