微信退款接口須要使用到證書,我參考微信的官方Demo進行,部分代碼以下:java
char[] password = config.getMchID().toCharArray();
InputStream certStream = config.getCertStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
複製代碼
上面的代碼,在本地調試的時候正常跑過,沒有出現任何異常,可是放到測試環境以後便會出現下面的異常,這三種異常都是從ks.load(certStream, password)這裏拋出來的。定位這個問題花費了一些時間,且讓我小小總結一下,供你們遇到相同問題時有個參考。api
java.io.IOException: Short read of DER length
at sun.security.util.DerInputStream.getLength(DerInputStream.java:582)
at sun.security.util.DerValue.init(DerValue.java:391)
at sun.security.util.DerValue.<init>(DerValue.java:332)
at sun.security.util.DerValue.<init>(DerValue.java:345)
at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1914)
at java.security.KeyStore.load(KeyStore.java:1445)
at com.lingyejun.authenticator.ReadPKCS12File$LoadCertInputStream.run(ReadPKCS12File.java:53)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
複製代碼
java.io.IOException: DerInputStream.getLength(): lengthTag=7, too big.
at sun.security.util.DerInputStream.getLength(DerInputStream.java:599)
at sun.security.util.DerValue.init(DerValue.java:391)
at sun.security.util.DerValue.<init>(DerValue.java:332)
at sun.security.util.DerValue.<init>(DerValue.java:345)
at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1914)
at java.security.KeyStore.load(KeyStore.java:1445)
at com.lingyejun.authenticator.ReadPKCS12File$LoadCertInputStream.run(ReadPKCS12File.java:53)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
複製代碼
java.io.IOException: toDerInputStream rejects tag type 54
at sun.security.util.DerValue.toDerInputStream(DerValue.java:874)
at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1915)
at java.security.KeyStore.load(KeyStore.java:1445)
at com.lingyejun.authenticator.ReadPKCS12File$LoadCertInputStream.run(ReadPKCS12File.java:53)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
複製代碼
結論:keyStore.load(InputStream stream, char[] password)中的InputStream在嘗試加載的過程當中,若是有其餘的線程正在使用或者進行一樣的讀加載,那麼就會拋出上面的異常。bash
package com.lingyejun.authenticator;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 模擬加載certStream問題
*
* @Author: lingyejun
* @Date: 2019/6/24
* @Describe:
* @Modified By:
*/
public class ReadPKCS12File {
// 線程個數
private static final int THREAD_POOL_SIZE = 10;
// 初始化線程池
private ExecutorService executorService = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
// HTTPS證書的本地路徑
private static final String CERT_LOCAL_PATH = "apiclient_cert.p12";
// HTTPS證書密碼,默認密碼等於商戶號MCHID
private static final String CERT_PASSWORD = "1509107311";
private static InputStream certStream = ReadPKCS12File.class.getClassLoader().getResourceAsStream(CERT_LOCAL_PATH);
public static void main(String[] args) {
ReadPKCS12File readPKCS12File = new ReadPKCS12File();
for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) {
readPKCS12File.executorService.execute(readPKCS12File.new LoadCertInputStream());
}
readPKCS12File.executorService.shutdown();
}
public class LoadCertInputStream implements Runnable {
@Override
public void run() {
// 證書
char[] password = CERT_PASSWORD.toCharArray();
InputStream certStream = ReadPKCS12File.certStream;
try {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
// 實例化密鑰庫 & 初始化密鑰工廠
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
// 建立 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
// 餘下代碼就不寫了,,,
System.out.println("初始化SSL成功!");
} catch (IOException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
}
}
複製代碼
知道問題以後,咱們只須要將certStream由全局惟一更改成方法的局部變量便可微信
InputStream certStream = ReadPKCS12File.certStream
複製代碼
改成dom
InputStream certStream = ReadPKCS12File.class.getClassLoader().getResourceAsStream(CERT_LOCAL_PATH)
複製代碼
微信的官方Demo中的,InputStream certStream = config.getCertStream(),這行代碼把我給'誤導'了,我是在外部讀取的pkcs12文件輸入流且config對象是單例的,致使多個線程共同訪問這行代碼時,certStream不能被正常加載,故出現了上面的問題。ide