好吧,起初听起来可能很奇怪,所以请忍受我:-)
我需要解决的问题是: 我需要以某种方式在Spring Boot应用程序中启用客户端身份验证,该方式允许客户端自己创建证书, 而 无需服务器使用服务器私钥对CSR进行签名。
我怎样才能实现这个目标?
背景:为什么需要这个?
我们已经设置了一个Spring Cloud Config Server。它包含许多不同应用程序的配置值。现在,我们只允许每个应用程序访问其自己的配置值。 解决此问题的最简单但安全的方法似乎是:
第7点将作为一个简单的实现Filter。
Filter
我要实现的目标基本上可以归结为一个问题: 不必从文件加载信任库,而必须基于安全配置存储中的数据在内存中创建信任库。 事实证明这有些棘手,但绝对有可能。
创建信任库很容易:
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); ts.load(null); for (Certificate cert : certList) { ts.setCertificateEntry(UUID.randomUUID().toString(), cert); }
但是,将其提供给SSL处理管道有点棘手。基本上,我们需要做的是提供一个X509ExtendedTrustManager使用上面创建的信任库的实现。 为了让SSL处理管道知道此实现,我们需要实现自己的提供程序:
X509ExtendedTrustManager
public class ReloadableTrustManagerProvider extends Provider { public ReloadableTrustManagerProvider() { super("ReloadableTrustManager", 1, "Provider to load client certificates from memory"); put("TrustManagerFactory." + TrustManagerFactory.getDefaultAlgorithm(), ReloadableTrustManagerFactory.class.getName()); } }
该提供程序又使用以下TrustManagerFactorySpi实现:
TrustManagerFactorySpi
public class ReloadableTrustManagerFactory extends TrustManagerFactorySpi { private final TrustManagerFactory originalTrustManagerFactory; public ReloadableTrustManagerFactory() throws NoSuchAlgorithmException { ProviderList originalProviders = ProviderList.newList( Arrays.stream(Security.getProviders()).filter(p -> p.getClass() != ReloadableTrustManagerProvider.class) .toArray(Provider[]::new)); Provider.Service service = originalProviders.getService("TrustManagerFactory", TrustManagerFactory.getDefaultAlgorithm()); originalTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm(), service.getProvider()); } @Override protected void engineInit(KeyStore keyStore) throws KeyStoreException { } @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException { } @Override protected TrustManager[] engineGetTrustManagers() { try { return new TrustManager[]{new ReloadableX509TrustManager(originalTrustManagerFactory)}; } catch (Exception e) { return new TrustManager[0]; } } }
有关更多originalTrustManagerFactory和ReloadableX509TrustManager以后的内容。 最后,我们需要以一种使提供程序成为默认提供程序的方式注册提供程序,以便SSL管道可以使用它:
originalTrustManagerFactory
ReloadableX509TrustManager
Security.insertProviderAt(new ReloadableTrustManagerProvider(), 1);
此代码可以在main之前执行SpringApplication.run。
main
SpringApplication.run
回顾一下:我们需要将我们的提供程序插入安全提供程序列表中。我们的提供者使用我们自己的信任管理器工厂来创建我们自己的信任管理器的实例。
仍然缺少两件事:
首先,实现(基于https://donneyfan.com/blog/dynamic-java-truststore-for-a-jax-ws- client):
public class ReloadableX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager { private final TrustManagerFactory originalTrustManagerFactory; private X509ExtendedTrustManager clientCertsTrustManager; private X509ExtendedTrustManager serverCertsTrustManager; private ArrayList<Certificate> certList; private static Log logger = LogFactory.getLog(ReloadableX509TrustManager.class); public ReloadableX509TrustManager(TrustManagerFactory originalTrustManagerFactory) throws Exception { try { this.originalTrustManagerFactory = originalTrustManagerFactory; certList = new ArrayList<>(); /* Example on how to load and add a certificate. Instead of loading it here, it should be loaded externally and added via addCertificates // Should get from secure configuration store String cert64 = "base64 encoded certificate"; byte encodedCert[] = Base64.getDecoder().decode(cert64); CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(encodedCert)); certList.add(cert); */ reloadTrustManager(); } catch (Exception e) { logger.fatal(e); throw e; } } /** * Removes a certificate from the pending list. Automatically reloads the TrustManager * * @param cert is not null and was already added * @throws Exception if cannot be reloaded */ public void removeCertificate(Certificate cert) throws Exception { certList.remove(cert); reloadTrustManager(); } /** * Adds a list of certificates to the manager. Automatically reloads the TrustManager * * @param certs is not null * @throws Exception if cannot be reloaded */ public void addCertificates(List<Certificate> certs) throws Exception { certList.addAll(certs); reloadTrustManager(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { clientCertsTrustManager.checkClientTrusted(chain, authType); } @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { clientCertsTrustManager.checkClientTrusted(x509Certificates, s, socket); } @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException { clientCertsTrustManager.checkClientTrusted(x509Certificates, s, sslEngine); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { serverCertsTrustManager.checkServerTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException { serverCertsTrustManager.checkServerTrusted(x509Certificates, s, socket); } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException { serverCertsTrustManager.checkServerTrusted(x509Certificates, s, sslEngine); } @Override public X509Certificate[] getAcceptedIssuers() { return ArrayUtils.addAll(serverCertsTrustManager.getAcceptedIssuers(), clientCertsTrustManager.getAcceptedIssuers()); } private void reloadTrustManager() throws Exception { KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); ts.load(null); for (Certificate cert : certList) { ts.setCertificateEntry(UUID.randomUUID().toString(), cert); } clientCertsTrustManager = getTrustManager(ts); serverCertsTrustManager = getTrustManager(null); } private X509ExtendedTrustManager getTrustManager(KeyStore ts) throws NoSuchAlgorithmException, KeyStoreException { originalTrustManagerFactory.init(ts); TrustManager tms[] = originalTrustManagerFactory.getTrustManagers(); for (int i = 0; i < tms.length; i++) { if (tms[i] instanceof X509ExtendedTrustManager) { return (X509ExtendedTrustManager) tms[i]; } } throw new NoSuchAlgorithmException("No X509TrustManager in TrustManagerFactory"); } }
此实现有一些值得注意的要点:
getAcceptedIssuers
为了完成所有这些工作,我们需要启用ssl客户端身份验证:
server.ssl.key-store: classpath:keyStore.p12 # secures our API with SSL. Needed, to enable client certificates handling server.ssl.key-store-password: very-secure server.ssl.client-auth: need
因为我们正在创建自己的信任库,所以不需要此设置server.ssl.trust-store及其相关设置
server.ssl.trust-store