| | |
| | | import cc.mrbird.febs.common.utils.SpringContextHolder; |
| | | import cc.mrbird.febs.mall.entity.MallOrderInfo; |
| | | import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper; |
| | | import cc.mrbird.febs.pay.model.FPCertificateVo; |
| | | import cc.mrbird.febs.pay.model.FPCertificates; |
| | | import cc.mrbird.febs.pay.model.FPEncryptCertificate; |
| | | import cc.mrbird.febs.pay.model.HeaderDto; |
| | | import cc.mrbird.febs.pay.service.WxFaPiaoService; |
| | | import cc.mrbird.febs.pay.util.RandomStringGenerator; |
| | |
| | | import java.security.cert.X509Certificate; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Base64; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { |
| | | public Map<String, Object> fapiaoCallBack(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, ParseException { |
| | | Map<String,Object> map = new HashMap<>(); |
| | | String signature = request.getHeader("Wechatpay-Signature"); |
| | | String timestamp = request.getHeader("Wechatpay-Timestamp"); |
| | |
| | | log.info("头信息---平台证书序列号:" + serial); |
| | | log.info("获取到的body信息:" + body); |
| | | //验签 |
| | | boolean signCheck = verifySign(request, body); |
| | | // boolean signCheck = signCheck(timestamp, nonce, requestBody, signature); |
| | | boolean signCheck = signCheck(timestamp, nonce, requestBody, signature); |
| | | log.info("验签结果:" + signCheck); |
| | | if (signCheck) { |
| | | // //解密参数 |
| | | // Resource resource = com.alibaba.fastjson.JSONObject.parseObject(com.alibaba.fastjson.JSONObject.toJSONString(requestBody.get("resource")), Resource.class); |
| | | // AesUtil aesUtil = new AesUtil(CommonParameters.apiV3Key.getBytes("utf-8")); |
| | | // String string = aesUtil.decryptToString(resource.getAssociated_data().getBytes("utf-8"), resource.getNonce().getBytes("utf-8"), resource.getCiphertext()); |
| | | // ComplaintInfo complaintInfo = JSONObject.parseObject(string, ComplaintInfo.class); |
| | | // //获取投诉详情 |
| | | // ComplaintDetail complaintDetail = CommonUtils.GetComplaintsInfo(complaintInfo.getComplaint_id()); |
| | | // data.put("code", "SUCCESS"); |
| | | // data.put("message", "成功"); |
| | | // return data; |
| | | try { |
| | | //解析请求体 |
| | | // Resource resource = com.alibaba.fastjson.JSONObject.parseObject(com.alibaba.fastjson.JSONObject.toJSONString(requestBody.get("resource")), Resource.class); |
| | |
| | | return map; |
| | | } |
| | | |
| | | // /** |
| | | // * 验证签名 |
| | | // * |
| | | // * @param timestamp 微信平台传入的时间戳 |
| | | // * @param nonce 微信平台传入的随机字符串 |
| | | // * @param requestBody 微信平台传入的消息体 |
| | | // * @param signature 微信平台传入的签名 |
| | | // * @return |
| | | // * @throws NoSuchAlgorithmException |
| | | // * @throws SignatureException |
| | | // * @throws IOException |
| | | // * @throws InvalidKeyException |
| | | // */ |
| | | // public static boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException { |
| | | // //构造验签名串 |
| | | // String signatureStr = timestamp + "\n" + nonce + "\n" + com.alibaba.fastjson.JSONObject.toJSONString(requestBody) + "\n"; |
| | | // // 加载SHA256withRSA签名器 |
| | | // Signature signer = Signature.getInstance("SHA256withRSA"); |
| | | // // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法) |
| | | // signer.initVerify(getCertificates()); |
| | | // // 把我们构造的验签名串更新到签名器中 |
| | | // signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); |
| | | // // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证 |
| | | // boolean result = signer.verify(Base64Utils.decodeFromString(signature)); |
| | | // return result; |
| | | // } |
| | | /** |
| | | * 验证签名 |
| | | * |
| | | * @param timestamp 微信平台传入的时间戳 |
| | | * @param nonce 微信平台传入的随机字符串 |
| | | * @param requestBody 微信平台传入的消息体 |
| | | * @param signature 微信平台传入的签名 |
| | | * @return |
| | | * @throws NoSuchAlgorithmException |
| | | * @throws SignatureException |
| | | * @throws IOException |
| | | * @throws InvalidKeyException |
| | | */ |
| | | public boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException, ParseException { |
| | | //构造验签名串 |
| | | String signatureStr = timestamp + "\n" + nonce + "\n" + com.alibaba.fastjson.JSONObject.toJSONString(requestBody) + "\n"; |
| | | // 加载SHA256withRSA签名器 |
| | | Signature signer = Signature.getInstance("SHA256withRSA"); |
| | | // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法) |
| | | signer.initVerify(getCertificates()); |
| | | // 把我们构造的验签名串更新到签名器中 |
| | | signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); |
| | | // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证 |
| | | boolean result = signer.verify(Base64Utils.decodeFromString(signature)); |
| | | return result; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取平台证书 |
| | | */ |
| | | // public static X509Certificate getCertificates() throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { |
| | | // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); |
| | | // CloseableHttpClient httpClient = CommonUtils.httpClient(); |
| | | // //请求URL |
| | | // HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates"); |
| | | // httpGet.setHeader("Accept", "application/json"); |
| | | // //生成签名 |
| | | // httpGet.setHeader("Authorization ", SignUtils.getSign("GET", HttpUrl.parse("https://api.mch.weixin.qq.com/v3/certificates"), "")); |
| | | // httpGet.setHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent"); |
| | | // //完成签名并执行请求 |
| | | // CloseableHttpResponse response = httpClient.execute(httpGet); |
| | | // X509Certificate x509Certificate = null; |
| | | // try { |
| | | // int statusCode = response.getStatusLine().getStatusCode(); |
| | | // if (statusCode == 200) { //处理成功 |
| | | //// System.out.println("success,return body = " + EntityUtils.toString(response.getEntity())); |
| | | // CertificateVo certificateVo = JSONObject.parseObject(EntityUtils.toString(response.getEntity()), CertificateVo.class); |
| | | // for (Certificates certificates : certificateVo.getData()) { |
| | | // if (format.parse(certificates.getEffective_time()).before(new Date()) && format.parse(certificates.getExpire_time()).after(new Date())) { |
| | | // EncryptCertificate encrypt_certificate = certificates.getEncrypt_certificate(); |
| | | // //解密 |
| | | // AesUtil aesUtil = new AesUtil(CommonParameters.apiV3Key.getBytes("utf-8")); |
| | | // String pulicKey = aesUtil.decryptToString(encrypt_certificate.getAssociated_data().getBytes("utf-8"), encrypt_certificate.getNonce().getBytes("utf-8"), encrypt_certificate.getCiphertext()); |
| | | // |
| | | // //获取平台证书 |
| | | // final CertificateFactory cf = CertificateFactory.getInstance("X509"); |
| | | // |
| | | // ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8)); |
| | | // |
| | | // x509Certificate = (X509Certificate) cf.generateCertificate(inputStream); |
| | | // } |
| | | // } |
| | | // return x509Certificate; |
| | | // } else if (statusCode == 204) { //处理成功,无返回Body |
| | | // System.out.println("success"); |
| | | // return x509Certificate; |
| | | // } else { |
| | | // System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity())); |
| | | // return x509Certificate; |
| | | // } |
| | | // } catch (GeneralSecurityException | ParseException e) { |
| | | // e.printStackTrace(); |
| | | // return null; |
| | | // } finally { |
| | | // response.close(); |
| | | // CommonUtils.after(httpClient); |
| | | // } |
| | | // } |
| | | public X509Certificate getCertificates() throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException, ParseException { |
| | | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); |
| | | CloseableHttpClient httpClient = HttpClients.createDefault(); |
| | | |
| | | /** |
| | | * 功能描述: 验证签名 |
| | | * 注意:使用微信支付平台公钥验签 |
| | | * Wechatpay-Signature 微信返签名 |
| | | * Wechatpay-Serial 微信平台证书序列号 |
| | | * |
| | | * @return java.lang.String |
| | | * @author 影子 |
| | | */ |
| | | @SneakyThrows |
| | | public boolean verifySign(HttpServletRequest request,String body) { |
| | | boolean verify = false; |
| | | PrivateKey privateKey = this.getPrivateKeyV3(); |
| | | String baseUrl = "https://api.mch.weixin.qq.com"; |
| | | String canonicalUrl = "/v3/certificates"; |
| | | String postStr = null; |
| | | try { |
| | | String wechatPaySignature = request.getHeader("Wechatpay-Signature"); |
| | | String wechatPayTimestamp = request.getHeader("Wechatpay-Timestamp"); |
| | | String wechatPayNonce = request.getHeader("Wechatpay-Nonce"); |
| | | String wechatPaySerial = request.getHeader("Wechatpay-Serial"); |
| | | //组装签名串 |
| | | String signStr = Stream.of(wechatPayTimestamp, wechatPayNonce, body) |
| | | .collect(Collectors.joining("\n", "", "\n")); |
| | | //获取平台证书 |
| | | AutoUpdateCertificatesVerifier verifier = getVerifier(wechatPaySerial); |
| | | //获取失败 验证失败 |
| | | if (verifier != null) { |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initVerify(verifier.getValidCertificate()); |
| | | //放入签名串 |
| | | signature.update(signStr.getBytes(StandardCharsets.UTF_8)); |
| | | verify = signature.verify(Base64.getDecoder().decode(wechatPaySignature.getBytes())); |
| | | } |
| | | } catch (InvalidKeyException e) { |
| | | e.printStackTrace(); |
| | | postStr = this.createAuthorization( |
| | | "GET", |
| | | baseUrl+canonicalUrl, |
| | | "", |
| | | privateKey |
| | | |
| | | ); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return verify; |
| | | } |
| | | |
| | | /** |
| | | * 保存微信平台证书 |
| | | */ |
| | | private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 功能描述:获取平台证书,自动更新 |
| | | * 注意:这个方法内置了平台证书的获取和返回值解密 |
| | | */ |
| | | public AutoUpdateCertificatesVerifier getVerifier(String mchSerialNo) { |
| | | AutoUpdateCertificatesVerifier verifier = null; |
| | | if (verifierMap.isEmpty() || !verifierMap.containsKey(mchSerialNo)) { |
| | | verifierMap.clear(); |
| | | try { |
| | | //传入证书 |
| | | PrivateKey privateKey = getPrivateKeyV3(); |
| | | //刷新 |
| | | PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey); |
| | | WechatPay2Credentials credentials = new WechatPay2Credentials(xcxProperties.getWecharpayMchid(), signer); |
| | | verifier = new AutoUpdateCertificatesVerifier(credentials |
| | | , xcxProperties.getWecharpaySecretV3().getBytes("utf-8")); |
| | | verifierMap.put(verifier.getValidCertificate().getSerialNumber()+"", verifier); |
| | | |
| | | // AutoUpdateCertificatesVerifier verifierNew = new AutoUpdateCertificatesVerifier( |
| | | // new WechatPay2Credentials( |
| | | // xcxProperties.getWecharpayMchid(), |
| | | // new PrivateKeySigner( |
| | | // mchSerialNo, |
| | | // privateKey)), |
| | | // xcxProperties.getWecharpaySecretV3().getBytes("utf-8")); |
| | | // new WechatPay2Validator(verifierNew).; |
| | | } catch (UnsupportedEncodingException e) { |
| | | e.printStackTrace(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } else { |
| | | verifier = verifierMap.get(mchSerialNo); |
| | | } |
| | | return verifier; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取公私钥.通过证书 |
| | | */ |
| | | private KeyStore store; |
| | | private final Object lock = new Object(); |
| | | public KeyPair createPKCS12(String keyAlias, String keyPass) { |
| | | // ClassPathResource resource = new ClassPathResource(xcxProperties.getCertLocalPath()); |
| | | ClassPathResource resource = new ClassPathResource("wxP12/apiclient_cert.p12"); |
| | | // File file = new File("src/main/resources/wxP12/apiclient_cert.p12"); |
| | | char[] pem = keyPass.toCharArray(); |
| | | //请求URL |
| | | HttpGet httpGet = new HttpGet(baseUrl+canonicalUrl); |
| | | httpGet.setHeader("Accept", "application/json"); |
| | | //生成签名 |
| | | httpGet.setHeader("Authorization ", "WECHATPAY2-SHA256-RSA2048"+postStr); |
| | | httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36"); |
| | | //完成签名并执行请求 |
| | | CloseableHttpResponse response = httpClient.execute(httpGet); |
| | | X509Certificate x509Certificate = null; |
| | | try { |
| | | synchronized (lock) { |
| | | if (store == null) { |
| | | synchronized (lock) { |
| | | store = KeyStore.getInstance("PKCS12"); |
| | | store.load(resource.getInputStream(), pem); |
| | | // store.load(new FileInputStream(file), pem); |
| | | int statusCode = response.getStatusLine().getStatusCode(); |
| | | if (statusCode == 200) { //处理成功 |
| | | // System.out.println("success,return body = " + EntityUtils.toString(response.getEntity())); |
| | | FPCertificateVo certificateVo = com.alibaba.fastjson.JSONObject.parseObject(EntityUtils.toString(response.getEntity()), FPCertificateVo.class); |
| | | for (FPCertificates certificates : certificateVo.getData()) { |
| | | if (format.parse(certificates.getEffective_time()).before(new Date()) |
| | | && format.parse(certificates.getExpire_time()).after(new Date())) { |
| | | FPEncryptCertificate encrypt_certificate = certificates.getEncrypt_certificate(); |
| | | //解密 |
| | | AesUtil aesUtil = new AesUtil(xcxProperties.getWecharpaySecretV3().getBytes("utf-8")); |
| | | String pulicKey = aesUtil.decryptToString( |
| | | encrypt_certificate.getAssociated_data().getBytes("utf-8"), |
| | | encrypt_certificate.getNonce().getBytes("utf-8"), |
| | | encrypt_certificate.getCiphertext()); |
| | | //获取平台证书 |
| | | final CertificateFactory cf = CertificateFactory.getInstance("X509"); |
| | | |
| | | ByteArrayInputStream inputStream = new ByteArrayInputStream(pulicKey.getBytes(StandardCharsets.UTF_8)); |
| | | |
| | | x509Certificate = (X509Certificate) cf.generateCertificate(inputStream); |
| | | } |
| | | } |
| | | return x509Certificate; |
| | | } else if (statusCode == 204) { //处理成功,无返回Body |
| | | System.out.println("success"); |
| | | return x509Certificate; |
| | | } else { |
| | | System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity())); |
| | | return x509Certificate; |
| | | } |
| | | X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias); |
| | | certificate.checkValidity(); |
| | | // 证书的序列号 也有用 50F37206347BCC9E6AC9860DAACE52AC035F7C24 |
| | | 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: " , e); |
| | | } catch (GeneralSecurityException | ParseException e) { |
| | | e.printStackTrace(); |
| | | return null; |
| | | } finally { |
| | | response.close(); |
| | | httpClient.close(); |
| | | } |
| | | } |
| | | |