我之前已经尝试过使用 Spring Boot 和嵌入式 Tomcat 服务器,但没有任何运气。我确实找到了一种使用 Spring Boot 和 Jetty 服务器来完成它的方法。 Tomcat 只能配置属性。该库不公开底层的 sslcontext,因此更难操作,对于这个用例,我找不到 tomcat 的方法。 Jetty 允许传递您可以操作的 sslcontext。
所以我创建了一个自定义的 TrustManager,它只是实际 TrustManager 的包装器。这个自定义的 TrustManager 可以将传入的方法调用委托给实际的 trustmanager。但是,此自定义信任管理器具有随时更改实际信任管理器的附加功能,例如当您想要更新信任库时。
我已经为这个用例创建了一个库,所以它可能很方便,但你可以在没有库的情况下完成同样的工作。这是没有使用附加库作为选项 1 的原版代码:
选项 1
HotSwappableX509ExtendedTrustManager
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedTrustManager;
import java.net.Socket;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Objects;
public class HotSwappableX509ExtendedTrustManager extends X509ExtendedTrustManager {
private X509ExtendedTrustManager trustManager;
public HotSwappableX509ExtendedTrustManager(X509ExtendedTrustManager trustManager) {
this.trustManager = Objects.requireNonNull(trustManager);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
trustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
trustManager.checkClientTrusted(chain, authType, socket);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
trustManager.checkClientTrusted(chain, authType, sslEngine);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
trustManager.checkServerTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
trustManager.checkServerTrusted(chain, authType, socket);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException {
trustManager.checkServerTrusted(chain, authType, sslEngine);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] acceptedIssuers = trustManager.getAcceptedIssuers();
return Arrays.copyOf(acceptedIssuers, acceptedIssuers.length);
}
public void setTrustManager(X509ExtendedTrustManager trustManager) {
this.trustManager = Objects.requireNonNull(trustManager);
}
}
用法
// Your key and trust manager created from your truststore file
X509ExtendedTrustManager aTrustManager = ...
// Wrapping it into your hot swappable trust manager
HotSwappableX509ExtendedTrustManager swappableTrustManager = new HotSwappableX509ExtendedTrustManager(aTrustManager);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{ swappableTrustManager }, null)
// Give the sslContext instance to your server
// After some time change the TrustManager with the following snippet:
X509ExtendedTrustManager anotherTrustManager = ... // Created from the updated truststore
// Set your new trust manager into your swappable managers
swappableTrustManager.setTrustManager(anotherTrustManager)
选项 2
如果您不想将自定义 (HotSwappableTrustManager) 代码添加到您的代码库中,您也可以使用我的库:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-jetty</artifactId>
<version>7.0.2</version>
</dependency>
用法
SSLFactory sslFactory = SSLFactory.builder()
.withSwappableIdentityMaterial()
.withIdentityMaterial("identity.jks", "password".toCharArray())
.withSwappableTrustMaterial()
.withTrustMaterial("truststore.jks", "password".toCharArray())
.build();
SSLContext sslContext = sslFactory.getSslContext();
// Give the sslContext instance to your server or client
// After some time change the KeyManager and TrustManager with the following snippet:
// swap identity and trust materials and reuse existing http client
KeyManagerUtils.swapKeyManager(sslFactory.getKeyManager().get(), anotherKeyManager);
TrustManagerUtils.swapTrustManager(sslFactory.getTrustManager().get(), anotherTrustManager);
// Cleanup old ssl sessions by invalidating them all. Forces to use new ssl sessions which will be created by the swapped KeyManager/TrustManager
SSLSessionUtils.invalidateCaches(sslContext);
这个项目正好演示了这个用例,见这里:GitHub - Instant server ssl reloading。该项目演示了两种更新 ssl 材料的方法:
你可以在这里找到图书馆:GitHub - SSLContext Kickstart
以下是完整的服务器配置:
这个例子可以很容易地用于选项 1 和选项 2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
SSLConfig 类
@Configuration
public class SSLConfig {
@Bean
public SSLFactory sslFactory(ApplicationProperty applicationProperty) {
return SSLFactory.builder()
.withSwappableIdentityMaterial()
.withIdentityMaterial(keyStorePath, keyStorePassword)
.withSwappableTrustMaterial()
.withTrustMaterial(trustStorePath, trustStorePassword)
.withNeedClientAuthentication(true)
.build();
}
@Bean
public SslContextFactory.Server sslContextFactory(SSLFactory sslFactory) {
return JettySslUtils.forServer(sslFactory);
}
@Bean
public X509ExtendedKeyManager keyManager(SSLFactory sslFactory) {
return sslFactory.getKeyManager().orElseThrow();
}
@Bean
public X509ExtendedTrustManager trustManager(SSLFactory sslFactory) {
return sslFactory.getTrustManager().orElseThrow();
}
@Bean
public SSLSessionContext serverSessionContext(SSLFactory sslFactory) {
return sslFactory.getSslContext().getServerSessionContext();
}
}
服务器配置类
@Configuration
public class ServerConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory(SslContextFactory.Server sslContextFactory) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
JettyServerCustomizer jettyServerCustomizer = server -> {
ServerConnector serverConnector = new ServerConnector(server, sslContextFactory);
serverConnector.setPort(8443);
server.setConnectors(new Connector[]{serverConnector});
};
factory.setServerCustomizers(Collections.singletonList(jettyServerCustomizer));
return factory;
}
}
以及 Swappable ssl 服务
@Service
public class SwappableSslService {
private final SSLSessionContext sslSessionContext;
private final X509ExtendedKeyManager swappableKeyManager;
private final X509ExtendedTrustManager swappableTrustManager;
public SwappableSslService(SSLSessionContext sslSessionContext,
X509ExtendedKeyManager swappableKeyManager,
X509ExtendedTrustManager swappableTrustManager) {
this.sslSessionContext = sslSessionContext;
this.swappableKeyManager = swappableKeyManager;
this.swappableTrustManager = swappableTrustManager;
}
public void updateSslMaterials(X509ExtendedKeyManager keyManager, X509ExtendedTrustManager trustManager) {
KeyManagerUtils.swapKeyManager(swappableKeyManager, keyManager);
TrustManagerUtils.swapTrustManager(swappableTrustManager, trustManager);
SSLSessionUtils.invalidateCaches(sslSessionContext);
}
}
现在您可以使用以下两个类中的任何一个轻松地动态更新信任库:AdminController 或 FileBasedSslUpdateService