Оплата WeChat на Java (1): подробное объяснение подписи версии API V3

Java OKHttp
Оплата WeChat на Java (1): подробное объяснение подписи версии API V3

1. Введение

В последнее время я забрасывал оплату WeChat, и сертификат довольно раздражает, поэтому необходимо поделиться некоторым опытом, чтобы уменьшить ваше наступление на яму при разработке оплаты WeChat. В настоящее время API оплаты WeChat был разработан дляV3версия в популярном стиле Restful.

微信支付V2与V3的区别

Сегодня давайте поделимся сложностями оплаты WeChat -подписать, хотя есть много полезных SDK, но если вы хотите глубже понять оплату WeChat, вам все равно нужно это понять.

2. Сертификат API

Чтобы обеспечить безопасность конфиденциальных данных о средствах, мы обеспечиваем надежность операций с денежными средствами в нашем бизнесе. В настоящее время предоставляется в официальном сертификате CA (сертификате API), выданном третьей стороной WeChat Pay.закрытый ключподписывать.Через торговую платформу вы можете настроить и получить сертификаты API..

API证书

Помните, что вам будет предложено скачать, когда вы устанавливаете его в первый раз, и загрузка не будет предоставлена ​​позже, пожалуйста, обратитесь к конкретной инструкции.

API证书说明

нашел после установкиzipРазархивируйте сжатый пакет, в нем много файлов, только нужно обратить внимание на разработку JAVAapiclient_cert.p12Этот файл сертификата подойдет, он содержит公私钥, нам нужно положить его на сервер и разобрать с помощью Java.p12файл, чтобы получить открытый ключ и закрытый ключ.

Обязательно обеспечить безопасность сертификата на стороне сервера, это предполагает безопасность средств.

Разобрать сертификат API

Следующим шагом является анализ сертификата.Методов анализа сертификата в интернете много.Здесь я использую более "обычный" метод анализа,используя пакет безопасности JDK.java.security.KeyStoreРазрешить.

Используется сертификат платежного API WeChatPKCS12алгоритм, проходимKeyStoreполучить носитель пары открытый-закрытый ключKeyPairи серийный номер сертификатаserialNumber, я инкапсулировал класс инструмента:

import org.springframework.core.io.ClassPathResource;

import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;

/**
 * KeyPairFactory
 *
 * @author dax
 * @since 13:41
 **/
public class KeyPairFactory {

    private KeyStore store;

    private final Object lock = new Object();

    /**
     * 获取公私钥.
     *
     * @param keyPath  the key path
     * @param keyAlias the key alias
     * @param keyPass  password
     * @return the key pair
     */
    public KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) {
        ClassPathResource resource = new ClassPathResource(keyPath);
        char[] pem = keyPass.toCharArray();
        try {
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(resource.getInputStream(), pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
            certificate.checkValidity();
            // 证书的序列号 也有用
            String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
            // 证书的 公钥
            PublicKey publicKey = certificate.getPublicKey();
            // 证书的私钥
            PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
    
            return new KeyPair(publicKey, storeKey);

        } catch (Exception e) {
            throw new IllegalStateException("Cannot load keys from store: " + resource, e);
        }
    }
}

Видно, что это модифицированная версия метода извлечения открытого и закрытого ключей, используемого в JWT в учебнике Spring Security от Fat Brother, Вы можете сравнить различия.

В этом методе есть три параметра, которые необходимо объяснить здесь:

  • keyPathсертификат APIapiclient_cert.p12изclasspathпуть, как правило, мы будем ставитьresourcesВ пути, конечно, вы можете изменить способ получения входного потока сертификата.
  • keyAliasПсевдоним сертификата, этого документа WeChat не существует, толстый брат получил значение DEBUG при загрузке сертификата, и значение зафиксировано какTenpay Certificate.
  • keyPassПароль сертификата, значение по умолчанию является бизнес-номер, в других конфигурациях также требуетсяmchid, ты используешьсупер администраторСтрока цифр в вашем профиле для входа в WeChat Merchant Platform.

3. Подпись V3

Версия WeChat Pay V3Сигнатура заключается в том, что когда мы вызываем конкретный API WeChat Pay, мы переносим определенную строку кода в заголовок HTTP-запроса для сервера WeChat Pay, чтобы проверить источник запроса, чтобы убедиться, что запрос является подлинным.

Формат подписи

Конкретный формат строки подписи, всего пять строк и одна строка не может быть меньше, каждая строка отделяется символом новой строки\nконец.

HTTP请求方法\n
URL\n
请求时间戳\n
请求随机串\n
请求报文主体\n
  • Метод HTTP-запросаМетод запроса, требуемый API-интерфейсом оплаты WeChat, который вы вызываете, например, платеж APP,POST.
  • URLНапример, платежный документ APPhttps://api.mch.weixin.qq.com/v3/pay/transactions/app, удалите часть имени домена, чтобы получить URL-адрес, участвующий в подписи. Если в запросе есть параметры запроса, '?' и соответствующая строка запроса должны быть добавлены в конец URL-адреса. здесь для/v3/pay/transactions/app.
  • Запросить временную меткуОтметка системного времени сервера гарантирует, что время сервера правильное и используется.System.currentTimeMillis() / 1000Возьми.
  • запросить случайную строкуНайдите класс инструментов для создания подобных593BEC0C930BF1AFEB40B4A08C8FB242Строка подойдет.
  • тело сообщения запросаеслиGETзапрос непосредственно нанулевой символ""; когда метод запросаPOSTилиPUT, пожалуйста, используйтеОтправить реальностьизJSONсообщение. API загрузки изображений, пожалуйста, используйтеmetaсоответствующийJSONсообщение.

Создать подпись

Затем мы используем пару закрытых ключей продавца в формате выше.Строка для подписиВыполните SHA256 с подписью RSA и выполните результат подписи.Кодировка Base64Получите значение подписи. Соответствующий основной код Java:

/**
 * V3  SHA256withRSA 签名.
 *
 * @param method       请求方法  GET  POST PUT DELETE 等
 * @param canonicalUrl 例如  https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
 * @param timestamp    当前时间戳   因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
 * @param nonceStr     随机字符串  要和TOKEN中的保持一致
 * @param body         请求体 GET 为 "" POST 为JSON
 * @param keyPair      商户API 证书解析的密钥对  实际使用的是其中的私钥
 * @return the string
 */
@SneakyThrows
String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
    String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
            .collect(Collectors.joining("\n", "", "\n"));
    Signature sign = Signature.getInstance("SHA256withRSA");
    sign.initSign(keyPair.getPrivate());
    sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
    return Base64Utils.encodeToString(sign.sign());
}

4. Используйте подпись

После того, как подпись будет сгенерирована, она будет объединена с некоторыми параметрами для формированияTokenразмещается в соответствующем HTTP-запросеAuthorizationВ заголовке запроса формат такой:

Authorization: WECHATPAY2-SHA256-RSA2048 {Token}

TokenОн состоит из следующих пяти частей:

  • Идентификатор продавца, инициировавшего запрос (включая продавцов с прямым подключением, поставщиков услуг или продавцов каналов)mchid

  • Сертификат API продавцасерийный номерserial_no, дляОбъявляет используемый сертификат

  • запросить случайную строкуnonce_str

  • отметка времениtimestamp

  • Значение подписиsignature

TokenСгенерированный основной код:

/**
 * 生成Token.
 *
 * @param mchId 商户号
 * @param nonceStr   随机字符串 
 * @param timestamp  时间戳
 * @param serialNo   证书序列号
 * @param signature  签名
 * @return the string
 */
String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
    final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
    // 生成token
    return String.format(TOKEN_PATTERN,
            wechatPayProperties.getMchId(),
            nonceStr, timestamp, serialNo, signature);
}

будет генерироватьTokenПодпись можно использовать, поместив ее в заголовок запроса в соответствии с указанным выше форматом.

5. Резюме

В этой статье мы провели полный анализ сложных подписей и использования подписей в WeChat Pay V 3. В то же время мы также объяснили анализ сертификатов API, которые, я считаю, могут помочь вам решить некоторые конкретные проблемы в развитие оплаты. Когда у меня будет время позже, я объясню проверку подписи, обратите внимание на:Код Фермер Маленький Толстый БратСвоевременный доступ к серии знаний.

关注公众号:Felordcn获取更多资讯

Личный блог: https://felord.cn