package cc.mrbird.febs.pay.service.impl; import cc.mrbird.febs.pay.service.LaKaLaService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.springframework.stereotype.Service; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.*; import java.security.*; import java.security.cert.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; @Slf4j @Service public class LaKaLaServiceImpl implements LaKaLaService { /** * 字符集固定 utf-8 */ public static final String ENCODING = "utf-8"; /** * API schema ,固定 LKLAPI-SHA256withRSA */ public final static String SCHEMA = "LKLAPI-SHA256withRSA"; /** * 接入 appid */ public static final String appid = "OP00000418"; // public static final String appid = "800000010334001"; /** * 商户证书序列号,和商户私钥对应 */ // public static final String mchSerialNo = "017d6ae9ad6e"; public static final String mchSerialNo = "01848940fd41"; /** * 商户证书私钥,用于请求签名 */ public static final String merchantPrivateKeyPath = "./src/api_private_key.pem"; /** * 拉卡拉公钥证书,用于response签名验证,务必区分测试环境和生产环境证书 */ public static final String lklCertificatePath = "./src/lkl-apigw-v2.cer"; /** * api请求地址 */ public final static String apiUrl = "https://test.wsmsd.cn/sit"; private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final SecureRandom RANDOM = new SecureRandom(); @Override public boolean verifyCreateOrder() { try { String apiPath = "/api/v3/labs/trans/micropay"; String body = "{\n" + " \"req_time\": \"20210907150256\",\n" + " \"version\": \"3.0\",\n" + " \"out_org_code\": \"OP00000003\",\n" + " \"req_data\": {\n" + " \"merchant_no\": \"8222900701107M5\",\n" + " \"term_no\": \"A1062976\",\n" + " \"out_trade_no\": \"FD660E1FAA3A4470933CDEDAE1EC1D8E\",\n" + " \"auth_code\": \"135178236713755038\",\n" + " \"total_amount\": \"23\",\n" + " \"location_info\": {\n" + " \"request_ip\": \"10.176.1.192\",\n" + " \"location\": \"+37.123456789,-121.123456789\"\n" + " },\n" + " \"out_order_no\": \"08F4542EEC6A4497BC419161747A92FA1\"\n" + " }\n" + "}"; String authorization = getAuthorization(body); HttpResponse response = post(apiUrl + apiPath, body, authorization); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("请求失败,statusCode " + response.getStatusLine() + IOUtils.toString(response.getEntity().getContent(), ENCODING)); return false; } String appid = getHeadValue(response, "Lklapi-Appid"); String lklapiSerial = getHeadValue(response, "Lklapi-Serial"); String timestamp = getHeadValue(response, "Lklapi-Timestamp"); String nonce = getHeadValue(response, "Lklapi-Nonce"); String signature = getHeadValue(response, "Lklapi-Signature"); String responseStr = IOUtils.toString(response.getEntity().getContent(), ENCODING); System.out.println("responseStr " + responseStr); String source = appid + "\n" + lklapiSerial + "\n" + timestamp + "\n" + nonce + "\n" + responseStr + "\n"; System.out.println("source " + source); X509Certificate lklCertificate = loadCertificate(new FileInputStream(new File(lklCertificatePath))); boolean verify = verify(lklCertificate, source.getBytes(ENCODING), signature); if (verify) { System.out.println("验签成功"); return true; } else { System.err.println("验签失败"); return false; } } catch (Exception e) { e.printStackTrace(); } return false; } // public static void main(String[] args) { // try { // String apiPath = "/api/v3/labs/trans/micropay"; // // String body = "{\n" + // " \"req_time\": \"20210907150256\",\n" + // " \"version\": \"3.0\",\n" + // " \"out_org_code\": \"OP00000003\",\n" + // " \"req_data\": {\n" + // " \"merchant_no\": \"822290070111135\",\n" + // " \"term_no\": \"29034705\",\n" + // " \"out_trade_no\": \"FD660E1FAA3A4470933CDEDAE1EC1D8E\",\n" + // " \"auth_code\": \"135178236713755038\",\n" + // " \"total_amount\": \"123\",\n" + // " \"location_info\": {\n" + // " \"request_ip\": \"10.176.1.192\",\n" + // " \"location\": \"+37.123456789,-121.123456789\"\n" + // " },\n" + // " \"out_order_no\": \"08F4542EEC6A4497BC419161747A92FA\"\n" + // " }\n" + // "}"; // // // String authorization = getAuthorization(body); // HttpResponse response = post(apiUrl + apiPath, body, authorization); // if (response.getStatusLine().getStatusCode() != 200) { // System.out.println("请求失败,statusCode " + response.getStatusLine() // + IOUtils.toString(response.getEntity().getContent(), ENCODING)); // return; // } // // String appid = getHeadValue(response, "Lklapi-Appid"); // String lklapiSerial = getHeadValue(response, "Lklapi-Serial"); // String timestamp = getHeadValue(response, "Lklapi-Timestamp"); // String nonce = getHeadValue(response, "Lklapi-Nonce"); // String signature = getHeadValue(response, "Lklapi-Signature"); // String responseStr = IOUtils.toString(response.getEntity().getContent(), ENCODING); // // System.out.println("responseStr " + responseStr); // // String source = appid + "\n" + lklapiSerial + "\n" + timestamp + "\n" + nonce + "\n" + responseStr + "\n"; // // System.out.println("source " + source); // // X509Certificate lklCertificate = loadCertificate(new FileInputStream(new File(lklCertificatePath))); // // boolean verify = verify(lklCertificate, source.getBytes(ENCODING), signature); // // if (verify) { // System.out.println("验签成功"); // // // TODO: 业务处理 // // } else { // System.err.println("验签失败"); // } // // } catch (Exception e) { // e.printStackTrace(); // } // // } public HttpResponse post(String url, String message, String authorization) throws Exception { SSLContext ctx = SSLContext.getInstance("TLS"); X509TrustManager tm = new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] xcs, String str) {} public void checkServerTrusted(X509Certificate[] xcs, String str) {} }; HttpClient http = new DefaultHttpClient(); ClientConnectionManager ccm = http.getConnectionManager(); ctx.init(null, new TrustManager[] { tm }, null); SSLSocketFactory ssf = new SSLSocketFactory(ctx); ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); SchemeRegistry registry = ccm.getSchemeRegistry(); registry.register(new Scheme("https", ssf,443)); HttpPost post = new HttpPost(url); StringEntity myEntity = new StringEntity(message, ENCODING); post.setEntity(myEntity); post.setHeader("Authorization", SCHEMA + " " + authorization); post.setHeader("Accept", "application/json"); post.setHeader("Content-Type", "application/json"); return http.execute(post); } protected long generateTimestamp() { return System.currentTimeMillis() / 1000; } protected String generateNonceStr() { char[] nonceChars = new char[32]; for (int index = 0; index < nonceChars.length; ++index) { nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); } return new String(nonceChars); } public final String getAuthorization(String body) throws IOException { String nonceStr = generateNonceStr(); long timestamp = generateTimestamp(); String message = appid + "\n" + mchSerialNo + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; System.out.println("getToken message : " + message); PrivateKey merchantPrivateKey = loadPrivateKey(new FileInputStream(new File(merchantPrivateKeyPath))); String signature = this.sign(message.getBytes(ENCODING), merchantPrivateKey); String authorization = "appid=\"" + appid + "\"," + "serial_no=\"" + mchSerialNo + "\"," + "timestamp=\"" + timestamp + "\"," + "nonce_str=\"" + nonceStr + "\"," + "signature=\"" + signature + "\""; System.out.println("authorization message :" + authorization); return authorization; } public String sign(byte[] message, PrivateKey privateKey) { try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(privateKey); sign.update(message); return new String(Base64.encodeBase64(sign.sign())); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); } catch (SignatureException e) { throw new RuntimeException("签名计算失败", e); } catch (InvalidKeyException e) { throw new RuntimeException("无效的私钥", e); } } private static String getHeadValue(HttpResponse response, String key) { return (response.containsHeader(key)) ? response.getFirstHeader(key).getValue() : ""; } private static boolean verify(X509Certificate certificate, byte[] message, String signature) { try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initVerify(certificate); sign.update(message); byte[] signatureB = Base64.decodeBase64(signature); return sign.verify(signatureB); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); } catch (SignatureException e) { throw new RuntimeException("签名验证过程发生了错误", e); } catch (InvalidKeyException e) { throw new RuntimeException("无效的证书", e); } } public static PrivateKey loadPrivateKey(InputStream inputStream) { try { ByteArrayOutputStream array = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) != -1) { array.write(buffer, 0, length); } String privateKey = array.toString("utf-8").replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", ""); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey))); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("当前Java环境不支持RSA", e); } catch (InvalidKeySpecException e) { throw new RuntimeException("无效的密钥格式"); } catch (IOException e) { throw new RuntimeException("无效的密钥"); } } public static X509Certificate loadCertificate(InputStream inputStream) { try { CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); cert.checkValidity(); return cert; } catch (CertificateExpiredException e) { throw new RuntimeException("证书已过期", e); } catch (CertificateNotYetValidException e) { throw new RuntimeException("证书尚未生效", e); } catch (CertificateException e) { throw new RuntimeException("无效的证书", e); } } }