微信支付分(三)--------授權/解除授權服務回調通知以及驗簽


在此講明,此回調文檔,可通用於微信支付分的所有回調,在此就只寫一次回調為例!!!!!!!!

一.在做授權回調通知我們需要了解以下要求

1.商戶系統對於服務授權/解除授權結果通知的內容一定要做簽名驗證,並校驗通知的信息是否與商戶側的信息一致,防止數據泄漏導致出現“假通知”,造成資金損失。(簽名)

2.該鏈接是通過聯系微信支付運營人員配置[商戶入駐配置申請表]提交service_notify_url設置,必須為https協議。如果鏈接無法訪問,商戶將無法接收到微信通知。 通知url必須為直接可訪問的url,不能攜帶參數。示例: “https://pay.weixin.qq.com/wxpay/pay.action”(配置的回調地址,配置好,微信會自動回調)

3.同時還需要設置APIV3密鑰

4.服務授權/解除授權結果通知是以POST方法訪問商戶設置的通知url,通知的數據以JSON格式通過請求主體(BODY)傳輸。通知的數據包括了加密的授權/解除授權結果詳情。(對返回的數據進行解密)

 

 

  /**
     * 授權/解除授權服務回調
     *
     * @param params
     * @return
     */
    @RequestMapping(value = "/xx/impowerBack.do", method = RequestMethod.POST)
    @SuppressWarnings("unchecked")
    public String weixinCallback(@RequestBody Map<String, Object> params) {
        LOGGER.info("======================================進入授權/解除授權服務回調");
        JSONObject resource = new JSONObject(params.get("resource"));

        //獲得驗簽字符串
        Map<String, String> certByAPI = null;
        try {
            certByAPI = Sign.getCertByAPIs(商戶MCHID, “"https://api.mch.weixin.qq.com/v3/certificates"”, 10, null, 商戶MCHSERIALNO, "D:\\xx\\xx\\apiclient_key.pem私鑰地址");
           
        } catch (Exception e) {
            e.printStackTrace();
        }

        //拼接驗簽字符串
        String signStr = certByAPI.get("Wechatpay-Timestamp") + "\n" + certByAPI.get("Wechatpay-Nonce") + "\n" + certByAPI.get("result") + "\n";
     

        //驗簽
        boolean verify = Sign.verify(signStr, certByAPI.get("Wechatpay-Signature"), "導出的公鑰路徑.txt");
        System.out.println("======================================verify=" + verify);
        if (verify == true) {
            try {

                String data = AesUtil.decryptToString("商戶設置Api3密鑰".getBytes(), resource.get("associated_data").toString().getBytes(), resource.get("nonce").toString().getBytes(), resource.get("ciphertext").toString());          
                            
            } catch (GeneralSecurityException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

 

 public static Map<String, String> getCertByAPIs(String merchantId, String url, int timeout, String body, String certSerialNo, String keyPath) throws UnsupportedEncodingException, Exception {
        String result = "";
        //創建http請求
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Content-Type", "application/json");
        httpGet.addHeader("Accept", "application/json");

        //設置認證信息
        httpGet.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + getToken("GET", url, null, merchantId, certSerialNo, keyPath));

        //設置請求器配置:如超時限制等
        RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
        httpGet.setConfig(config);
        List<X509Certificate> x509Certs = new ArrayList<X509Certificate>();
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = httpClient.execute(httpGet);
            Header[] allHeaders = response.getAllHeaders();
            Map<String, String> headers = new HashMap<String, String>();
            for (int i = 0; i < allHeaders.length; i++) {
                String key = (String) allHeaders[i].getName();
                String value = allHeaders[i].getValue();
                headers.put(key, value);
            }
            String Nonce = headers.get("Wechatpay-Nonce");
            String Signature = headers.get("Wechatpay-Signature");
            String Timestamp = headers.get("Wechatpay-Timestamp");
            System.out.println(Timestamp);

            int statusCode = response.getStatusLine().getStatusCode();
            HttpEntity httpEntity = response.getEntity();
            result = EntityUtils.toString(httpEntity, "UTF-8");
            headers.put("result", result);
            //String result1 = Timestamp + "\n" + Nonce + "\n" + result + "\n";
            System.out.println("下載平台證書返回結果:" + result);


            return headers;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("下載平台證書返回結果:" + e);
        }
        return null;
    }

  

  //method(請求類型GET、POST url(請求url) body(請求body,GET請求時body傳"",POST請求時body為請求參數的json串)  merchantId(商戶號) certSerialNo(API證書序列號) keyPath(API證書路徑)
    public static String getToken(String method, String url, String body, String merchantId, String certSerialNo, String keyPath) throws Exception {
        String signStr = "";
        HttpUrl httpurl = HttpUrl.parse(url);
//        String nonceStr = getNonceStr();
        String nonceStr = "隨機字符串";
        long timestamp = System.currentTimeMillis() / 1000;
        if (StringUtils.isEmpty(body)) {
            body = "";
        }
        String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"), keyPath);
        signStr = "mchid=\"" + merchantId
                + "\",nonce_str=\"" + nonceStr
                + "\",timestamp=\"" + timestamp
                + "\",serial_no=\"" + certSerialNo
                + "\",signature=\"" + signature + "\"";
        LOGGER.info("Authorization Token:" + signStr);
        System.out.println("signStr:--" + signStr);
        return signStr;
    }

 

 public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

  

public static String sign(byte[] message, String keyPath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message);
        return Base64.encodeBase64String(sign.sign());
    }

  

 /**
     * 驗簽
     *
     * @param srcData
     * @param signedData
     * @param publicKeyPath
     * @return
     */
    public static boolean verify(String srcData, String signedData, String publicKeyPath) {
        if (srcData == null || signedData == null || publicKeyPath == null) {
            return false;
        }
        try {
            PublicKey publicKey = readPublic(publicKeyPath);
            Signature sign = Signature.getInstance(ALGORITHM);
            sign.initVerify(publicKey);
            sign.update(srcData.getBytes(CHARSET_ENCODING));
            System.out.println("publicKey = " + publicKey);
            return sign.verify(java.util.Base64.getDecoder().decode(signedData));
        } catch (Exception e) {
            System.out.println("e = " + e);
            e.printStackTrace();
        }
        return false;
    }

  

 /**
     * 讀取公鑰
     *
     * @param publicKeyPath
     * @return
     */
    private static PublicKey readPublic(String publicKeyPath) {
        if (publicKeyPath == null) {
            return null;
        }
        PublicKey pk = null;
        FileInputStream bais = null;
        try {
            CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");
            bais = new FileInputStream(publicKeyPath);
            X509Certificate cert = (X509Certificate) certificatefactory.generateCertificate(bais);
            pk = cert.getPublicKey();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (bais != null) {
                try {
                    bais.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return pk;
    }

  

獲取公鑰代碼

 public static List<X509Certificate> getCertByAPI(String merchantId, String url, int timeout, String body, String certSerialNo, String keyPath) throws UnsupportedEncodingException, Exception {
        String result = "";
        //創建http請求
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Content-Type", "application/json");
        httpGet.addHeader("Accept", "application/json");

        //設置認證信息
        httpGet.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + getToken("GET", url, null, merchantId, certSerialNo, keyPath));

        //設置請求器配置:如超時限制等
        RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
        httpGet.setConfig(config);
        List<X509Certificate> x509Certs = new ArrayList<X509Certificate>();
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            HttpEntity httpEntity = response.getEntity();
            result = EntityUtils.toString(httpEntity, "UTF-8");
            if (statusCode == 200) {
                LOGGER.info("下載平台證書返回結果:" + result);
                List<CertificateItem> certList = new ArrayList<CertificateItem>();
                JSONObject json = JSONObject.parseObject(result);
                LOGGER.info("查詢結果json字符串轉證書List:" + json.get("data"));
                JSONArray jsonArray = (JSONArray) json.get("data");
                for (int i = 0; i < jsonArray.size(); i++) {
                    CertificateItem certificateItem = new CertificateItem();
                    EncryptedCertificateItem encryptCertificate = new EncryptedCertificateItem();
                    JSONObject bo = JSONObject.parseObject(jsonArray.get(i).toString());
                    certificateItem.setSerial_no(bo.get("serial_no").toString());
                    certificateItem.setEffective_time(bo.get("effective_time").toString());
                    certificateItem.setExpire_time(bo.get("expire_time").toString());
                    JSONObject encryptBo = JSONObject.parseObject(bo.get("encrypt_certificate").toString());
                    encryptCertificate.setAlgorithm(encryptBo.get("algorithm").toString());
                    encryptCertificate.setNonce(encryptBo.get("nonce").toString());
                    encryptCertificate.setAssociated_data(encryptBo.get("associated_data").toString());
                    encryptCertificate.setCiphertext(encryptBo.get("ciphertext").toString());
                    certificateItem.setEncrypt_certificate(encryptCertificate);
                    certList.add(certificateItem);
                }
                LOGGER.info("證書List:" + certList);

                List<PlainCertificateItem> plainList = decrypt(certList, response);
                if (CollectionUtils.isNotEmpty(plainList)) {
                    LOGGER.info("平台證書開始保存");
                    //x509Certs = saveCertificate(plainList);
                }
            }
            response.close();
            httpClient.close(); //throw
            return x509Certs;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("下載平台證書返回結果:" + e);
        }
        return x509Certs;
    }

  

 private static List<PlainCertificateItem> decrypt(List<CertificateItem> certList, CloseableHttpResponse response) throws GeneralSecurityException, IOException {
        List<PlainCertificateItem> plainCertificateList = new ArrayList<PlainCertificateItem>();
        AesUtil aesUtil = new AesUtil(("Api3密鑰").getBytes(StandardCharsets.UTF_8));
        for (CertificateItem item : certList) {
            PlainCertificateItem bo = new PlainCertificateItem();
            bo.setSerialNo(item.getSerial_no());
            bo.setEffectiveTime(item.getEffective_time());
            bo.setExpireTime(item.getExpire_time());
            LOGGER.info("平台證書密文解密");
            bo.setPlainCertificate(aesUtil.decryptToString(item.getEncrypt_certificate().getAssociated_data().getBytes(StandardCharsets.UTF_8),
                    item.getEncrypt_certificate().getNonce().getBytes(StandardCharsets.UTF_8), item.getEncrypt_certificate().getCiphertext()));
            LOGGER.info("平台證書公鑰明文:" + bo.getPlainCertificate());
            System.out.println("平台證書公鑰明文:" + bo.getPlainCertificate());
            plainCertificateList.add(bo);
        }
        return plainCertificateList;
    }

  

對微信返回數據進行解密

/**
 * 對加密的授權/解除授權結果進行解密
 *
 * @param
 * @return
 */
public class AesUtil {
    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;

    public static String decryptToString(byte[] aesKey,byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException, IOException {
        if (aesKey.length != KEY_LENGTH_BYTE) {
            throw new IllegalArgumentException("無效的ApiV3Key,長度必須為32個字節");
        }
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(aesKey, "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)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM