使用 SSL 套裝組合保護 Spring Boot 3 應用程式
一、簡介
管理 Spring Boot 應用程式中的安全通訊通常涉及處理複雜的配置。挑戰通常始於處理信任資料,例如憑證和私鑰,這些資料採用各種格式,如 JKS、PKCS #12 或 PEM。對於如何處理這些格式,每種格式都有自己的一套要求。
幸運的是,Spring Boot 3.1 引入了 SSL Bundles,這項功能旨在簡化這些複雜性。在本教程中,我們將探討什麼是 SSL 捆綁包以及它們如何簡化 Spring Boot 應用程式的 SSL 設定任務。
2. Spring Boot SSL 捆綁包
通常,一旦我們獲得了信任材料,我們需要將其轉換為應用程式可以使用的 Java 物件。這通常意味著處理諸如用於存儲密鑰材料的java.security.KeyStore 、用於管理此密鑰材料javax.net.ssl.KeyManager以及用於創建安全套接字連接javax.net.ssl.SSLContext之類的類別。
每個類別都需要另一層理解和配置,使得過程繁瑣且容易出錯。各種 Spring Boot 元件可能還需要深入研究不同的抽象層來應用這些設置,從而為任務增加了另一層難度。
SSL 捆綁包將所有信任材料和組態設定(例如金鑰庫、憑證和私密金鑰)封裝到一個易於管理的單元中。配置 SSL 捆綁包後,即可將其應用於一個或多個網路連接,無論它們是傳入還是傳出。
SSL 捆綁包的設定屬性位於application.yaml或application.properties設定檔中的spring.ssl.bundle前綴下。
讓我們從 JKS 捆綁包開始。我們使用spring.ssl.bundle.jks來設定使用 Java Keystore 檔案的套件:
spring:
ssl:
bundle:
jks:
server:
key:
alias: "server"
keystore:
location: "classpath:server.p12"
password: "secret"
type: "PKCS12"
對於 PEM 捆綁包,我們使用spring.ssl.bundle.pem來使用 PEM 編碼的文字檔案配置捆綁包:
spring:
ssl:
bundle:
pem:
client:
truststore:
certificate: "classpath:client.crt"
配置這些捆綁包後,它們可以跨微服務應用 - 無論是需要安全存取資料庫的庫存服務、需要安全 API 呼叫的用戶身份驗證服務,還是與支付網關安全通訊的支付處理服務。
Spring Boot 根據 SSL Bundle 配置自動建立 Java 對象,如KeyStore 、 KeyManager和SSLContext 。這消除了手動建立和管理這些物件的需要,使過程更加簡單且不易出錯。
3. 使用 SSL 捆綁包保護RestTemplate
讓我們從在使用RestTemplate bean 的同時利用 SSL 捆綁包開始。為此,我們將使用範例 Spring Boot 應用程序,但首先,我們需要產生將用作 SSL 捆綁包的金鑰。
我們將使用openssl二進位(通常與 git 一起安裝)透過從專案根目錄執行以下命令來產生金鑰:
$ openssl req -x509 -newkey rsa:4096 -keyout src/main/resources/key.pem -out src/main/resources/cert.pem -days 365 -passout pass:FooBar
現在,讓我們將此金鑰轉換為 PKCS12 格式:
$ openssl pkcs12 -export -in src/main/resources/cert.pem -inkey src/main/resources/key.pem -out src/main/resources/keystore.p12 -name secure-service -passin pass:FooBar -passout pass:FooBar
因此,我們擁有配置 SSL 捆綁包的一切;讓我們在application.yml檔案中定義一個名為“secure-service”的套件:
spring:
ssl:
bundle:
jks:
secure-service:
key:
alias: "secure-service"
keystore:
location: "classpath:keystore.p12"
password: "FooBar"
type: "PKCS12"
接下來,我們可以透過呼叫setSslBundle()方法在RestTemplate上設定捆綁包:
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
return restTemplateBuilder.setSslBundle(sslBundles.getBundle("secure-service")).build();
}
最後,我們可以使用配置好的RestTemplate bean 來呼叫 API:
@Service
public class SecureServiceRestApi {
private final RestTemplate restTemplate;
@Autowired
public SecureServiceRestApi(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String fetchData(String dataId) {
ResponseEntity<String> response = restTemplate.exchange(
"https://secure-service.com/api/data/{id}",
HttpMethod.GET,
null,
String.class,
dataId
);
return response.getBody();
}
}
Spring Boot 應用程式中的 SSL Bundle 用於驗證secure-service的證書,確保加密且安全的通訊通道。但是,這並不限制我們在 API 端使用客戶端憑證進行身份驗證。稍後我們將看到如何取得SSLContext來配置自訂客戶端。
4.利用 Spring Boot 的自動設定SSLBundles
在 Spring Boot 的 SSL Bundles 之前,開發人員通常會使用支援 SSL 配置的經典 Java 類別:
-
java.security.KeyStore:這些實例用作金鑰庫和信任庫,有效地充當加密金鑰和憑證的安全儲存庫。 -
javax.net.ssl.KeyManager和javax.net.ssl.TrustManager:這些實例分別管理 SSL 通訊期間的金鑰和信任決策。 -
javax.net.ssl.SSLContext:這些實例充當SSLEngine和SSLSocket物件的工廠,協調 SSL 配置在運行時的實作方式。
Spring Boot 3.1 引進了分為 Java 介面的結構化抽象層:
-
SslStoreBundle:提供通往包含加密金鑰和可信任憑證的KeyStore物件的閘道。 -
SslManagerBundle:協調並提供管理KeyManager和TrustManager物件的方法。 -
SslBundle:作為一站式商店,將所有這些功能聚合到與 SSL 生態系統的統一互動模型中。
隨後,Spring Boot 自動配置SslBundles bean。因此,我們可以方便地將SslBundle實例注入任何 Spring Bean 中。這對於為舊程式碼庫和自訂 REST 用戶端配置安全通訊非常有用。
例如,讓我們考慮自訂安全HttpClient需要自訂SSLContext :
@Component
public class SecureRestTemplateConfig {
private final SSLContext sslContext;
@Autowired
public SecureRestTemplateConfig(SslBundles sslBundles) throws NoSuchSslBundleException {
SslBundle sslBundle = sslBundles.getBundle("secure-service");
this.sslContext = sslBundle.createSslContext();
}
@Bean
public RestTemplate secureRestTemplate() {
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create().setSslContext(this.sslContext).build();
HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
HttpClient httpClient = HttpClients.custom().setConnectionManager(cm).evictExpiredConnections().build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
}
在上面的程式碼中,我們將SslBundles實例注入到Autowired建構子中。實際上, SslBundles為我們提供了對所有配置的 SSL 捆綁包的存取。因此,我們檢索secure-service包並建立上下文。稍後,我們使用SSLContext實例建立自訂HttpClient並應用它來建立RestTemplate bean。
5. 將 SSL 捆綁包與資料服務結合使用
不同的資料服務具有不同程度的 SSL 配置選項,從而在配置過程中造成複雜性。
SSL Bundles 引入了一種更統一的方法來跨各種資料服務進行 SSL 配置:
- 卡桑德拉:
spring.cassandra.ssl - 沙發底座:
spring.couchbase.env.ssl - Elasticsearch:
spring.elasticsearch.restclient.ssl - MongoDB:
spring.data.mongodb.ssl - Redis:
spring.data.redis.ssl
現在,大多數這些服務都支援*.ssl.enabled屬性。此屬性啟動客戶端庫中的 SSL 支持,利用 Java 運行時的cacerts中找到的信任材料。
此外, *.ssl.bundle屬性允許我們應用命名的 SSL 捆綁包來自訂信任材料,從而實現跨多個服務連接的一致性和可重複使用性。
對於此範例,我們假設有一個名為mongodb-ssl-bundle的 SSL 套件。此捆綁包包含必要的信任材料,以確保與 MongoDB 實例的連線安全。
讓我們定義application.yml檔案:
spring:
data:
mongodb:
ssl:
enabled: true
bundle: mongodb-ssl-bundle
只要新增這些屬性,Spring Boot 應用程式中的 MongoDB 用戶端程式庫就會自動使用mongodb-ssl-bundle中指定的 SSL 上下文和信任材質。
6. 將 SSL 捆綁包與嵌入式伺服器一起使用
使用 SSL 套裝組合還可以簡化在 Spring Boot 中管理嵌入式 Web 伺服器的 SSL 設定。
傳統上, server.ssl.*屬性用於設定每個單獨的 SSL 配置。透過 SSL 捆綁包,可以將配置分組在一起,然後在多個連接之間重複使用,從而減少出錯的可能性並簡化整體管理。
首先,讓我們探討一下傳統的個人財產方法:
server:
ssl:
key-alias: "server"
key-password: "keysecret"
key-store: "classpath:server.p12"
key-store-password: "storesecret"
client-auth: NEED
另一方面,SSL Bundle 方法允許封裝相同的配置:
spring:
ssl:
bundle:
jks:
web-server:
key:
alias: "server"
password: "keysecret"
keystore:
location: "classpath:server.p12"
password: "storesecret"
現在,我們可以使用定義的套件來保護我們的 Web 伺服器:
server:
ssl:
bundle: "web-server"
client-auth: NEED
雖然這兩種方法都可以保護嵌入式 Web 伺服器,但 SSL 捆綁方法對於設定管理來說更有效。此功能也可用於其他配置,例如management.server.ssl和spring.rsocket.server.ssl 。
七、結論
在本文中,我們探索了 Spring Boot 中新的 SSL Bundles 功能,該功能可以簡化和統一配置信任材料的過程。
與傳統的server.ssl.*屬性相比,SSL 捆綁包提供了一種結構化的方式來管理金鑰庫和信任庫。這對於減少錯誤配置風險和提高跨多個服務管理 SSL 的效率特別有利。
此外, SSL 捆綁包非常適合集中管理,允許在應用程式的不同部分重複使用同一個捆綁包。
透過合併 SSL 套裝組合,開發人員不僅可以簡化設定流程,還可以提升 Spring Boot 應用程式中嵌入式 Web 伺服器的安全性。
與往常一樣,整個程式碼範例可以在 GitHub 上找到。