New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |