Валидация SSL-сертификатов в Spring Boot: предотвращение использования просроченных
Узнайте, почему Spring Boot принимает просроченные SSL-сертификаты и как реализовать правильную валидацию срока действия с помощью пользовательских TrustManager и SSLContext.
Почему Spring Boot/Tomcat принимает просроченные SSL-сертификаты, несмотря на предупреждения, и как правильно проверять срок действия сертификатов?
У меня есть базовое SSL-приложение клиент-сервер на Spring Boot с настройками по умолчанию. Хотя приложения успешно обмениваются данными и не работают с неправильными сертификатами, они также принимают и обрабатывают запросы при использовании просроченных самоподписанных сертификатов. В логах отображаются только предупреждения:
CLIENT:
2025-11-11T06:24:55.050-08:00 WARN 4476 --- [SpringSslBundleClient] [ main] org.apache.tomcat.util.net.SSLUtilBase : The trusted certificate with alias [serveralias] and DN [CN=localhost, OU=Server, O=Examples, L=, ST=CA, C=U] is not valid due to [NotAfter: Mon Nov 10 16:13:13 PST 2025]. Certificates signed by this trusted certificate WILL be accepted
SERVER:
2025-11-11T06:20:47.285-08:00 WARN 13292 --- [ main] org.apache.tomcat.util.net.SSLUtilBase : The trusted certificate with alias [clientalias] and DN [CN=localhost, OU=Server, O=Examples, L=, ST=CA, C=U] is not valid due to [NotAfter: Mon Nov 10 16:13:25 PST 2025]. Certificates signed by this trusted certificate WILL be accepted
Конфигурация сервера:
spring.ssl.bundle.jks.testapp.truststore.location=classpath:certs/serverTruststore.p12
spring.ssl.bundle.jks.testapp.truststore.password=secretstuff
spring.ssl.bundle.jks.testapp.truststore.type=PKCS12
server.ssl.bundle=testapp
server.ssl.client-auth=need
Конфигурация клиента:
spring.ssl.bundle.jks.clientapp.key.alias=clientalias
spring.ssl.bundle.jks.clientapp.keystore.location=classpath:clientKeystore.p12
spring.ssl.bundle.jks.clientapp.keystore.password=secretstuff
spring.ssl.bundle.jks.clientapp.keystore.type=PKCS12
spring.ssl.bundle.jks.clientapp.truststore.location=classpath:clientTruststore.p12
spring.ssl.bundle.jks.clientapp.truststore.password=secretstuff
spring.ssl.bundle.jks.clientapp.truststore.type=PKCS12
server.ssl.bundle=clientapp
Я изучил исходный код Tomcat и обнаружил, что метод checkTrustStoreEntries только регистрирует предупреждения о просроченных сертификатах, но не предотвращает их использование. Необходимо ли реализовывать собственный RestClient или RestTemplate для правильной проверки срока действия сертификатов в Spring Boot? Я использую Java 21 и spring-starter-web 3.5.7.
Spring Boot/Tomcat принимает просроченные SSL-сертификаты, потому что проверка доверительного хранилища (truststore) по умолчанию только регистрирует предупреждения, но не обеспечивает проверки срока действия сертификатов. Это поведение обусловлено реализацией X509TrustManager по умолчанию в Java, которая может быть настроена на принятие просроченных сертификатов, когда они явно доверены в доверительном хранилище. Для правильной проверки срока действия сертификата необходимо реализовать пользовательскую логику проверки, которая проверяет период действия сертификата во время SSL-рукопожатия, либо через пользовательский TrustManager, либо с использованием SSLContext Java с настройками строгой проверки.
Содержание
- Понимание поведения проверки SSL по умолчанию
- Почему принимаются просроченные сертификаты
- Методы правильной проверки сертификатов
- Реализация пользовательского TrustManager
- Параметры конфигурации для строгой проверки
- Мониторинг и срок действия сертификата
- Лучшие практики управления SSL-сертификатами
Понимание поведения проверки SSL по умолчанию
Поведение проверки SSL по умолчанию в Spring Boot/Tomcat разработано так, чтобы быть гибким, когда сертификаты явно доверены в доверительном хранилище. Когда вы импортируете сертификат в доверительное хранилище, Java рассматривает его как доверенный корневой сертификат, и по умолчанию система будет принимать сертификаты, подписанные этим доверенным корневым сертификатом, даже если сам корневой сертификат просрочен.
Из результатов исследования мы видим, что предупреждающие сообщения, которые вы наблюдаете, генерируются классом SSLUtilBase Tomcat в методе checkTrustStoreEntries. Как вы обнаружили в исходном коде Tomcat, этот метод только регистрирует предупреждения, но не предотвращает использование просроченных сертификатов.
Ключевое понимание из обсуждения на Stack Overflow заключается в том, что когда сертификат находится в доверительном хранилище, Java рассматривает его как доверенный корневой сертификат, и сертификаты, подписанные этим корневым сертификатом, будут приниматься независимо от даты истечения срока действия корневого сертификата. Это сознательный выбор дизайна для поддержки сценариев, в которых центры сертификации (CA) могут просрочиться, но их подписанные сертификаты все еще действительны.
Почему принимаются просроченные сертификаты
Несколько факторов способствуют тому, что Spring Boot/Tomcat принимает просроченные сертификаты:
Truststore как центр сертификации
Когда вы импортируете сертификат в доверительное хранилище, Java рассматривает его как центр сертификации (CA), а не просто как доверенную конечную точку. Это означает, что любой сертификат, подписанный этим CA, будет доверен, даже если сам сертификат CA просрочен.
Согласно документации Spring Boot SSL, доверительные хранилища используются для проверки как клиентских, так и серверных сертификатов в сценариях двусторонней SSL-аутентификации.
Поведение X509TrustManager по умолчанию
Реализация X509TrustManager по умолчанию в Java не обеспечивает строгие проверки срока действия для доверенных корневых сертификатов. Как упоминается в обсуждении на Stack Exchange по безопасности, криптографическая архитектура Java (JCA) не пытается проверить срок действия сертификатов в доверительном хранилище.
Конфигурация SSLContext
Конфигурация SSLContext по умолчанию в Spring Boot не переопределяет стандартное поведение проверки доверия. В конфигурации SSL Spring Boot показано, что хотя вы можете настроить хранилища ключей и доверительные хранилища, основная логика проверки остается стандартным поведением Java.
Методы правильной проверки сертификатов
Для правильной проверки срока действия сертификата необходимо реализовать пользовательскую логику проверки. Вот несколько подходов:
Реализация пользовательского TrustManager
Пользовательский TrustManager позволяет переопределить поведение проверки сертификата по умолчанию. GitHub gist предоставляет хороший пример того, как создать IgnoreExpirationTrustManager, который можно изменить для обеспечения строгих проверок срока действия.
public class StrictExpirationTrustManager implements X509TrustManager {
private final X509TrustManager defaultTrustManager;
public StrictExpirationTrustManager(TrustManagerFactory tmf) throws NoSuchAlgorithmException {
this.defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// Проверка срока действия для каждого сертификата в цепочке
for (X509Certificate cert : chain) {
cert.checkValidity();
}
defaultTrustManager.checkServerTrusted(chain, authType);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// Проверка срока действия для каждого сертификата в цепочке
for (X509Certificate cert : chain) {
cert.checkValidity();
}
defaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
}
Использование SSLContext с пользовательским TrustManager
Вы можете настроить SSLContext с вашим пользовательским TrustManager:
@Configuration
public class SslConfig {
@Value("${server.ssl.trust-store}")
private String trustStorePath;
@Value("${server.ssl.trust-store-password}")
private String trustStorePassword;
@Bean
public SSLContext sslContext() throws Exception {
KeyStore trustStore = KeyStore.getInstance("PKCS12");
try (InputStream is = new ClassPathResource(trustStorePath).getInputStream()) {
trustStore.load(is, trustStorePassword.toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new StrictExpirationTrustManager(tmf)}, null);
return sslContext;
}
}
Настройка RestClient/RestTemplate
Для HTTP-клиентов вы можете настроить SSL-контекст, используемый RestClient или RestTemplate:
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(SSLContext sslContext) {
return RestClient.builder()
.client(HttpClient.create()
.secure(sslContext))
.build();
}
}
Параметры конфигурации для строгой проверки
Подход с системными свойствами
Вы можете установить системные свойства для обеспечения более строгой проверки SSL:
System.setProperty("javax.net.ssl.trustStore", trustStorePath);
System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
System.setProperty("com.sun.net.ssl.checkRevocation", "true");
Однако, как отмечено в ответе на Stack Overflow, флаг -Dcom.sun.net.ssl.checkRevocation=false не влияет на проверки срока действия, только на проверки отзыва.
Горячая перезагрузка SSL в Spring Boot 3.2.0
Spring Boot 3.2.0 представил возможности горячей перезагрузки SSL, которые могут быть полезны для управления сертификатами. Как упоминается в блоге Spring, это позволяет поворачивать SSL-материалы доверия без перезапуска приложения.
spring:
ssl:
bundle:
jks:
server:
truststore:
location: classpath:serverTruststore.p12
password: ${SSL_TRUSTSTORE_PASSWORD}
type: PKCS12
Мониторинг и срок действия сертификата
Программная проверка сертификатов
Вы можете реализовать мониторинг для проверки сроков действия сертификатов программным путем. GitHub gist содержит примеры того, как проверить сроки действия SSL-сертификатов в Java.
public static void checkCertificateExpiry(String keystorePath, String password, String alias) {
try (InputStream is = new FileInputStream(keystorePath)) {
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(is, password.toCharArray());
X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);
Date expiryDate = cert.getNotAfter();
Date currentDate = new Date();
long daysUntilExpiry = (expiryDate.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24);
System.out.println("Срок действия сертификата истекает через: " + daysUntilExpiry + " дней");
} catch (Exception e) {
e.printStackTrace();
}
}
Автоматизированные решения мониторинга
Для производственных сред рассмотрите возможность реализации автоматизированных систем мониторинга, которые предупреждают вас до истечения срока действия сертификатов. Руководство infopackets предлагает настроить мониторинг, аналогичный Nagios, для отслеживания сроков действия сертификатов.
Лучшие практики управления SSL-сертификатами
Регулярная ротация сертификатов
Реализуйте процесс регулярной ротации сертификатов до истечения срока их действия. В обсуждении на Server Fault упоминается, что хотя Tomcat может перезагружать сертификаты без перезапуска, все равно необходима правильная планировка.
Сертификаты, специфичные для среды
Используйте разные сертификаты для сред разработки, тестирования и производства. Это предотвращает случайное использование просроченных сертификатов в производственной среде.
Проверка сертификатов в CI/CD
Добавьте проверки проверки сертификатов в ваш CI/CD конвейер, чтобы убедиться, что сертификаты действительны перед развертыванием.
Документация и график ротации
Ведите документацию по графикам ротации сертификатов и процессам. Статья поддержки Axway предоставляет руководство по использованию keytool для проверки сроков действия сертификатов.
Источники
- Springboot/Tomcat Accepts Expired Certs - Stack Overflow
- Spring Boot SSL: How To Trust All Valid Certificates - Stack Overflow
- Specifying trust store information in Spring boot application.properties - Stack Overflow
- SSL hot reload in Spring Boot 3.2.0 - Spring Blog
- Securing Spring Boot Applications With SSL - Spring Blog
- Ignoring Expired TLS Certificates in Java - GitHub Gist
- Enable Java to permit expired certificate - Stack Overflow
- KB: TLS accepts expired certificate in Java - Hazelcast Wiki
- How to check SSL certificate expiration date programmatically in Java - Stack Overflow
- How to Fix: Java ‘Expired or not-yet-valid Certificate’ Error - InfoPackets
Заключение
Spring Boot/Tomcat принимает просроченные SSL-сертификаты, потому что проверка доверительного хранилища по умолчанию рассматривает доверенные сертификаты как центры сертификации и не обеспечивает проверку срока действия этих доверенных корневых сертификатов. Для правильной проверки срока действия сертификата необходимо реализовать пользовательскую логику TrustManager, которая явно проверяет периоды действия сертификата во время SSL-рукопожатия.
Ключевые рекомендации:
- Реализуйте пользовательский TrustManager, который переопределяет методы
checkServerTrustedиcheckClientTrustedдля проверки срока действия сертификата - Используйте SSLContext с вашим пользовательским TrustManager для конфигураций как сервера, так и клиента
- Добавьте мониторинг для отслеживания сроков действия сертификатов и предупреждения до истечения срока
- Рассмотрите возможность использования горячей перезагрузки SSL в Spring Boot 3.2.0+ для более простого управления сертификатами
- Документируйте процессы ротации сертификатов и планируйте регулярную ротацию
Хотя реализация пользовательской проверки добавляет сложности, она обеспечивает правильную проверку безопасности и предотвращает потенциальные проблемы безопасности из-за принятия просроченных сертификатов.