Spring调用双向SSL(X509) REST API:多别名JKS密钥库异常问题
问题:Spring调用双向SSL认证API时,多别名JKS的密钥密码兼容问题
我需要用Spring调用采用双向SSL(X509)认证的REST API,且必须使用包含多个别名的JKS密钥库文件。我编写了如下代码来创建RestTemplate:
KeyStore clientTrustStore = getStore(trustStore, pwdTrustStore.toCharArray()); KeyStore clientKeyStore = getStore(keyStore, pwdKeyStore.toCharArray()); SSLContext sslContext = new SSLContextBuilder() .setKeyStoreType(JAVA_KEYSTORE) //.setKeyManagerFactoryAlgorithm(...) .loadKeyMaterial(clientKeyStore, aliasPwd.toCharArray(), new KeyStrategy(alias)) .loadTrustMaterial(clientTrustStore, new TrustSelfSignedStrategy()) .build(); SSLConnectionSocketFactory sslConFactory = new SSLConnectionSocketFactory(sslContext); try (CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConFactory).build()) { restTemplateBuilder.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient)); } this.restTemplate = restTemplateBuilder.build();
在JDK8环境下,这段代码底层会使用SunX509KeyManagerImpl类解析密钥库,其核心构造逻辑如下:
SunX509KeyManagerImpl(KeyStore ks, char[] password) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { credentialsMap = new HashMap<String,X509Credentials>(); serverAliasCache = Collections.synchronizedMap( new HashMap<String,String[]>()); if (ks == null) { return; } for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); ) { String alias = aliases.nextElement(); if (!ks.isKeyEntry(alias)) { continue; } Key key = ks.getKey(alias, password); if (key instanceof PrivateKey == false) { continue; } Certificate[] certs = ks.getCertificateChain(alias); if ((certs == null) || (certs.length == 0) || !(certs[0] instanceof X509Certificate)) { continue; } if (!(certs instanceof X509Certificate[])) { Certificate[] tmp = new X509Certificate[certs.length]; System.arraycopy(certs, 0, tmp, 0, certs.length); certs = tmp; } X509Credentials cred = new X509Credentials((PrivateKey)key, (X509Certificate[])certs); credentialsMap.put(alias, cred); if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { SSLLogger.fine("found key for : " + alias, (Object[])certs); } } }
从这段代码可以看到,它会枚举密钥库中的所有别名,并用传入的同一个密码尝试获取每个别名对应的密钥。当某个别名的密钥密码与传入的密码不匹配时,就会抛出java.security.UnrecoverableKeyException: Cannot recover key异常。
我有几个疑问:
- 为什么SunX509KeyManagerImpl不能只过滤指定的别名进行密钥验证?
- 是否JKS密钥库中的所有别名必须使用同一个密码?
- 我了解可以通过
setKeyManagerFactoryAlgorithm自定义KeyManager来替代SunX509KeyManagerImpl,但不想重复造轮子,有没有更优的实现方案?
内容的提问来源于stack exchange,提问作者Choco
相关产品推荐
相关产品推荐

