以编程方式使用自定义 SSLContext 配置 Spring 引导(对于 mTLS)
Configure Spring Boot's with custom SSLContext programmatically (for mTLS)
问题
以编程方式配置 Spring 引导以使用我的自定义 SSLContext
。并用于 mTLS。
上下文
Spring 的 documentation 仅提供了一种如何配置 SSL 的清晰方法(通过 application.properties
):
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.trust-store=classpath:truststore.jks
...
但是,此解决方案缺乏 深度,因为在某些情况下,我想利用自定义我自己的 SSLContext
。例如,将 mTLS 配置为不仅信任通过 keytool
生成的单个证书,而且信任我的自签名证书和放置在 Java 的默认 TrustStore (lib/security/cacerts
) 中的证书。
当然,我可以使用已经提到的 keytool
来组合它们,但我正在寻找更灵活的方法,因此提供了我自己的 SSLContext
.
Spring 提供了一个关于 Configure the Web Server 的部分,它说使用 TomcatServletWebServerFactory
或 ConfigurableServletWebServerFactory
之类的东西,但它们并没有真正深入。
我试过创建 Component
:
@Component
public class CustomServletCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private static final String SERVER_CERTIFICATE_PATH = "identity.jks";
private static final char[] PASSWORD = "secret".toCharArray();
private static final String CLIENT_CERTIFICATE_PATH = "certs/client.cer";
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setSslStoreProvider(new SslStoreProvider() {
@Override
public KeyStore getKeyStore() throws Exception {
var certificateAsInputStream = this.getClass().getClassLoader().getResourceAsStream(SERVER_CERTIFICATE_PATH);
var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(certificateAsInputStream, PASSWORD);
return keyStore;
}
@Override
public KeyStore getTrustStore() throws Exception {
var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
var organisationRootCertBytes = this.getClass().getClassLoader().getResourceAsStream(CLIENT_CERTIFICATE_PATH).readAllBytes();
var certificateFactory = CertificateFactory.getInstance("X.509");
var certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(organisationRootCertBytes));
keyStore.setCertificateEntry("server", certificate);
return keyStore;
}
});
}
}
但是没有用。
不幸的是,Spring Boot + Tomcat 无法做到这一点。它没有注入 SSLContext 或其他属性(例如 SSLServerSocketFactory 或 TrustManager 或 KeyManager)的选项。
但是,如果您仍想使用 Spring 引导并想对其进行全面配置,并且您不关心 Spring 引导在幕后使用哪种服务器,我会推荐码头.
下面是一个基本的实现,你可以如何使用 Spring Boot + Jetty 来实现:
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import java.util.Collections;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public SslContextFactory.Server sslContextFactory() {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setSslContext(sslContext);
sslContextFactory.setIncludeProtocols(protocols);
sslContextFactory.setIncludeCipherSuites(ciphers);
sslContextFactory.setNeedClientAuth(true);
return sslContextFactory;
}
@Bean
public ConfigurableServletWebServerFactory webServerFactory(SslContextFactory.Server sslContextFactory) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.setPort(8443);
JettyServerCustomizer jettyServerCustomizer = server -> server.setConnectors(new Connector[]{new ServerConnector(server, sslContextFactory)});
factory.setServerCustomizers(Collections.singletonList(jettyServerCustomizer));
return factory;
}
}
您还需要告诉 spring 不再使用 tomcat 并切换到码头。您可以通过将以下代码片段添加到您的 pom 中来做到这一点:
<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>
问题
以编程方式配置 Spring 引导以使用我的自定义 SSLContext
。并用于 mTLS。
上下文
Spring 的 documentation 仅提供了一种如何配置 SSL 的清晰方法(通过 application.properties
):
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.trust-store=classpath:truststore.jks
...
但是,此解决方案缺乏 深度,因为在某些情况下,我想利用自定义我自己的 SSLContext
。例如,将 mTLS 配置为不仅信任通过 keytool
生成的单个证书,而且信任我的自签名证书和放置在 Java 的默认 TrustStore (lib/security/cacerts
) 中的证书。
当然,我可以使用已经提到的 keytool
来组合它们,但我正在寻找更灵活的方法,因此提供了我自己的 SSLContext
.
Spring 提供了一个关于 Configure the Web Server 的部分,它说使用 TomcatServletWebServerFactory
或 ConfigurableServletWebServerFactory
之类的东西,但它们并没有真正深入。
我试过创建 Component
:
@Component
public class CustomServletCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private static final String SERVER_CERTIFICATE_PATH = "identity.jks";
private static final char[] PASSWORD = "secret".toCharArray();
private static final String CLIENT_CERTIFICATE_PATH = "certs/client.cer";
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setSslStoreProvider(new SslStoreProvider() {
@Override
public KeyStore getKeyStore() throws Exception {
var certificateAsInputStream = this.getClass().getClassLoader().getResourceAsStream(SERVER_CERTIFICATE_PATH);
var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(certificateAsInputStream, PASSWORD);
return keyStore;
}
@Override
public KeyStore getTrustStore() throws Exception {
var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
var organisationRootCertBytes = this.getClass().getClassLoader().getResourceAsStream(CLIENT_CERTIFICATE_PATH).readAllBytes();
var certificateFactory = CertificateFactory.getInstance("X.509");
var certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(organisationRootCertBytes));
keyStore.setCertificateEntry("server", certificate);
return keyStore;
}
});
}
}
但是没有用。
不幸的是,Spring Boot + Tomcat 无法做到这一点。它没有注入 SSLContext 或其他属性(例如 SSLServerSocketFactory 或 TrustManager 或 KeyManager)的选项。
但是,如果您仍想使用 Spring 引导并想对其进行全面配置,并且您不关心 Spring 引导在幕后使用哪种服务器,我会推荐码头.
下面是一个基本的实现,你可以如何使用 Spring Boot + Jetty 来实现:
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import java.util.Collections;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public SslContextFactory.Server sslContextFactory() {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setSslContext(sslContext);
sslContextFactory.setIncludeProtocols(protocols);
sslContextFactory.setIncludeCipherSuites(ciphers);
sslContextFactory.setNeedClientAuth(true);
return sslContextFactory;
}
@Bean
public ConfigurableServletWebServerFactory webServerFactory(SslContextFactory.Server sslContextFactory) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.setPort(8443);
JettyServerCustomizer jettyServerCustomizer = server -> server.setConnectors(new Connector[]{new ServerConnector(server, sslContextFactory)});
factory.setServerCustomizers(Collections.singletonList(jettyServerCustomizer));
return factory;
}
}
您还需要告诉 spring 不再使用 tomcat 并切换到码头。您可以通过将以下代码片段添加到您的 pom 中来做到这一点:
<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>