在Spring Boot应用程序中指定信任存储信息的方法

80

我正在使用springBootVersion 1.2.0.RELEASE

我尝试通过application.properties配置我的密钥库和信任库。

当我添加以下设置时,我可以让密钥库工作,但是信任库不起作用。

server.ssl.key-store=classpath:foo.jks
server.ssl.key-store-password=password
server.ssl.key-password=password
server.ssl.trust-store=classpath:foo.jks
server.ssl.trust-store-password=password

然而,如果我通过Gradle添加信任库:

bootRun {
    jvmArgs = [ "-Djavax.net.ssl.trustStore=c://foo.jks", "-Djavax.net.ssl.trustStorePassword=password"]
}

它运行得非常好。

有人使用application.properties来做信任存储吗?


1
你使用了哪个嵌入式容器,并且你是如何确定它无法工作的? - Andy Wilkinson
10个回答

48

如果您需要进行REST调用,可以使用以下方法。

这适用于通过RestTemplate进行的呼出调用。

像这样声明RestTemplate bean。

@Configuration
public class SslConfiguration {
    @Value("${http.client.ssl.trust-store}")
    private Resource keyStore;
    @Value("${http.client.ssl.trust-store-password}")
    private String keyStorePassword;

    @Bean
    RestTemplate restTemplate() throws Exception {
        SSLContext sslContext = new SSLContextBuilder()
                .loadTrustMaterial(
                        keyStore.getURL(),
                        keyStorePassword.toCharArray()
                ).build();
        SSLConnectionSocketFactory socketFactory = 
                new SSLConnectionSocketFactory(sslContext);
        HttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(socketFactory).build();
        HttpComponentsClientHttpRequestFactory factory = 
                new HttpComponentsClientHttpRequestFactory(httpClient);
        return new RestTemplate(factory);
    }
}

http.client.ssl.trust-storehttp.client.ssl.trust-store-password指向以JKS格式存储的证书信任库及其密码。

此操作将覆盖Spring Boot提供的RestTemplate bean并使其使用所需的信任库。


1
基本上这是信任存储路径。但是,您可能会将受信任的证书存储在与自己的证书和私钥相同的文件中。如果是这种情况,您可以在那里使用密钥库。您可以在此处找到更多详细信息 https://dev59.com/eWw15IYBdhLWcg3w4_tL#6341566 - Sasha Shpota
2
http.client.ssl.trust-storehttp.client.ssl.trust-store-password 是自定义属性,对吗? - riccardo.cardin
1
@riccardo.cardin 是的,可以。如果你想的话,可以使用不同的名称。 - Sasha Shpota
6
@OleksandrShpotaжҳҜзұ»еһӢResourceзҡ„е®Ңж•ҙеҗҚз§°еҗ—пјҹжҲ‘йңҖиҰҒеҜје…Ҙе“ӘдёӘзұ»еҲ°жҲ‘зҡ„зұ»дёӯпјҹ - riccardo.cardin
6
org.springframework.core.io.Resource 是Spring框架中的一个接口,用于抽象化不同来源的资源,如文件、classpath下的资源、URL等。 - Sasha Shpota
显示剩余5条评论

27

我在使用Spring Boot、Spring Cloud(微服务)和自签名SSL证书时遇到了同样的问题。应用程序属性中的密钥库可以直接使用,但信任库无法使用。

最终,我将密钥库和信任库的配置都保留在application.properties文件中,并添加了一个单独的配置Bean以通过系统配置信任库属性。

@Configuration
public class SSLConfig {
    @Autowired
    private Environment env;

    @PostConstruct
    private void configureSSL() {
      //set to TLSv1.1 or TLSv1.2
      System.setProperty("https.protocols", "TLSv1.1");

      //load the 'javax.net.ssl.trustStore' and
      //'javax.net.ssl.trustStorePassword' from application.properties
      System.setProperty("javax.net.ssl.trustStore", env.getProperty("server.ssl.trust-store")); 
      System.setProperty("javax.net.ssl.trustStorePassword",env.getProperty("server.ssl.trust-store-password"));
    }
}

23

我有同样的问题,我会尝试详细解释一下。

我正在使用spring-boot 1.2.2-RELEASE,并在Tomcat和Undertow上尝试了相同的结果。

在application.yml中定义信任存储库,如下:

server:
  ssl:
    trust-store: path-to-truststore...
    trust-store-password: my-secret-password...

无法正常工作,而:

$ java -Djavax.net.debug=ssl -Djavax.net.ssl.trustStore=path-to-truststore... -Djavax.net.ssl.trustStorePassword=my-secret-password... -jar build/libs/*.jar  

工作得非常好。

在运行时最简单的方法是在客户端中启用ssl-debug,以查看差异。当工作正常时(即使用-D标志),类似以下内容将写入控制台(在处理第一个请求期间):

trustStore is: path-to-truststore...
trustStore type is : jks
trustStore provider is :
init truststore
adding as trusted cert:
  Subject: C=..., ST=..., O=..., OU=..., CN=...
  Issuer:  C=..., ST=..., O=..., OU=..., CN=...
  Algorithm: RSA; Serial number: 0x4d2
  Valid from Wed Oct 16 17:58:35 CEST 2013 until Tue Oct 11 17:58:35 CEST 2033

如果没有 -D 标志,我会得到:

trustStore is: /Library/Java/JavaVirtualMachines/jdk1.8.0_11.jdk/Contents/Home/jre/lib/security/cacerts
trustStore type is : jks
trustStore provider is :
init truststore
adding as trusted cert: ... (one for each CA-cert in the defult truststore)

...并且当执行请求时,我会得到异常:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

希望这能更好地帮助理解问题!


6
有人知道为什么使用 application.yml 的方法不起作用吗? - Sasha Shpota
22
!!!请注意!!!非常重要的精度:您必须在JAR之前传递信任存储,否则Spring上下文将接管并且您将继续收到相同的错误。 - avi.elkharrat
2
请查看命令行:$ java -Djavax.net.ssl.trustStore=path-to-truststore -jar blabla.jar,信任存储库应该在 jar 文件之前。如果您先传递 jar 文件再传递信任存储库,它将无法正常工作。我猜,Spring 框架有一种机制可以覆盖和锁定上下文。 - avi.elkharrat
2
@avi.elkharrat,感谢您指出这一点。由于这个事实,我浪费了太多的时间。结果发现没有其他人可以责怪,只能责怪我自己;他们在手册中非常清楚地指出java [options] -jar <jarfile> [args...]。在-jar <jarfile>之后的所有内容都会进入应用程序而不是Java本身... - Iorweth333
显示剩余2条评论

20

Java属性"javax.net.ssl.trustStore"和"javax.net.ssl.trustStorePassword"与Spring Boot的"application.properties"("application.yml")中的"server.ssl.trust-store"和"server.ssl.trust-store-password"不对应。

因此,你不能简单地通过在"application.properties"("application.yml")中设置"server.ssl.trust-store"和"server.ssl.trust-store-password"来设置"javax.net.ssl.trustStore"和"javax.net.ssl.trustStorePassword"。

另一种设置"javax.net.ssl.trustStore"和"javax.net.ssl.trustStorePassword"的方法是使用Spring Boot的外置配置

以下是我实现的摘录:

Params类保存了外部设置。

@Component
@ConfigurationProperties("params")
public class Params{

    //default values, can be override by external settings
    public static String trustStorePath = "config/client-truststore.jks";
    public static String trustStorePassword = "wso2carbon";
    public static String keyStorePath = "config/wso2carbon.jks";
    public static String keyStorePassword = "wso2carbon";
    public static String defaultType = "JKS";
    
    public void setTrustStorePath(String trustStorePath){
        Params.trustStorePath = trustStorePath;
    }
           
    public void settrustStorePassword(String trustStorePassword){
        Params.trustStorePassword=trustStorePassword;
    }

    public void setKeyStorePath(String keyStorePath){
        Params.keyStorePath = keyStorePath;
    }
         
    public void setkeyStorePassword(String keyStorePassword){
        Params.keyStorePassword = keyStorePassword;
    }
    
    public void setDefaultType(String defaultType){
        Params.defaultType = defaultType;
    }

KeyStoreUtil类负责设置“javax.net.ssl.trustStore”和“javax.net.ssl.trustStorePassword”

public class KeyStoreUtil {
    
    public static void setTrustStoreParams() {
        File filePath = new File( Params.trustStorePath);
        String tsp = filePath.getAbsolutePath();
        System.setProperty("javax.net.ssl.trustStore", tsp);
        System.setProperty("javax.net.ssl.trustStorePassword", Params.trustStorePassword);
        System.setProperty("javax.net.ssl.keyStoreType", Params.defaultType);

    }

    public static void setKeyStoreParams() {
        File filePath = new File(Params.keyStorePath);
        String ksp = filePath.getAbsolutePath();
        System.setProperty("Security.KeyStore.Location", ksp);
        System.setProperty("Security.KeyStore.Password", Params.keyStorePassword);

    }     
}

在启动函数内执行setter函数。

@SpringBootApplication
@ComponentScan("com.myapp.profiles")
public class ProfilesApplication {

    public static void main(String[] args) {
        KeyStoreUtil.setKeyStoreParams();
        KeyStoreUtil.setTrustStoreParams();
        SpringApplication.run(ProfilesApplication.class, args);
    }
}

2018-10-03 编辑

您可能还希望采用注释“PostConstruct”作为执行setter方法的替代方案。

import javax.annotation.PostConstruct;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages={"com.xxx"})
public class GateApplication {

    public static void main(String[] args) {
        SpringApplication.run(GateApplication.class, args);
    }
    
    @PostConstruct
    void postConstruct(){
        setTrustStoreParams();
        setKeyStoreParams();
    }
    
    
    private static void setTrustStoreParams() {
        File filePath = new File( Params.trustStorePath);
        String tsp = filePath.getAbsolutePath();
        System.setProperty("javax.net.ssl.trustStore", tsp);
        System.setProperty("javax.net.ssl.trustStorePassword", Params.trustStorePassword);
        System.setProperty("javax.net.ssl.keyStoreType", Params.defaultType);

    }

    private static void setKeyStoreParams() {
        File filePath = new File(Params.keyStorePath);
        String ksp = filePath.getAbsolutePath();
        System.setProperty("Security.KeyStore.Location", ksp);
        System.setProperty("Security.KeyStore.Password", Params.keyStorePassword);

    }
}

应用程序.yml

---
 params: 
   trustStorePath: config/client-truststore.jks
   trustStorePassword: wso2carbon
   keyStorePath: config/wso2carbon.jks
   keyStorePassword: wso2carbon
   defaultType: JKS
---

最后,在运行环境(部署服务器)中,您需要在与Jar归档文件相同的文件夹下创建一个名为“config”的文件夹。

在“config”文件夹中,存储“application.yml”、“client-truststore.jks”和“wso2carbon.jks”。完成!

2018-11-27更新有关Spring Boot 2.x.x

从Spring Boot 2.x.x开始,不再支持静态属性,请参见此处。我个人认为这不是一个好的举措,因为必须沿着引用链进行复杂的更改...

无论如何,实现摘录可能如下所示

“Params”类

    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import lombok.Data;
    
    /**
     * Params class represent all config parameters that can 
     * be external set by spring xml file
     */
    
    @Component
    @ConfigurationProperties("params")
    @Data
    public class Params{
    
        //default values, can be override by external settings
        public String trustStorePath = "config/client-truststore.jks";
        public String trustStorePassword = "wso2carbon";
        public String keyStorePath = "config/wso2carbon.jks";
        public String keyStorePassword = "wso2carbon";
        public String defaultType = "JKS";  
}

'Springboot应用程序类'(带有'PostConstruct')

import java.io.File;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages={"com.xx.xx"})
public class BillingApplication {
    
    @Autowired
    Params params;
    
    public static void main(String[] args) {
        SpringApplication.run(BillingApplication.class, args);
    }
    
    @PostConstruct
    void postConstruct() {
        
        // set TrustStoreParams
        File trustStoreFilePath = new File(params.trustStorePath);
        String tsp = trustStoreFilePath.getAbsolutePath();
        System.setProperty("javax.net.ssl.trustStore", tsp);
        System.setProperty("javax.net.ssl.trustStorePassword", params.trustStorePassword);
        System.setProperty("javax.net.ssl.keyStoreType", params.defaultType);
        // set KeyStoreParams
        File keyStoreFilePath = new File(params.keyStorePath);
        String ksp = keyStoreFilePath.getAbsolutePath();
        System.setProperty("Security.KeyStore.Location", ksp);
        System.setProperty("Security.KeyStore.Password", params.keyStorePassword);
    }
    
}

11
我也遇到了Spring Boot和嵌入式Tomcat的同样问题。
据我所知,这些属性仅设置Tomcat配置参数。 根据Tomcat文档,这仅用于客户端身份验证(即双向SSL),而不用于验证远程证书:
truststoreFile - 用于验证客户端证书的信任库文件。

https://tomcat.apache.org/tomcat-8.0-doc/config/http.html

为了配置HttpClient的信任库,它在很大程度上取决于您使用的HttpClient实现。例如,默认情况下,Spring Boot使用基于标准J2SE类(如java.net.HttpURLConnection)的SimpleClientHttpRequestFactory来为RestTemplate配置信任库。
我根据Apache HttpClient文档和以下帖子提出了一种解决方案: http://vincentdevillers.blogspot.pt/2013/02/configure-best-spring-resttemplate.html http://literatejava.com/networks/ignore-ssl-certificate-errors-apache-httpclient-4-4/ 基本上,这允许创建一个RestTemplate bean,仅信任由配置的信任库中的根CA签名的证书。
@Configuration
public class RestClientConfig {

    // e.g. Add http.client.ssl.trust-store=classpath:ssl/truststore.jks to application.properties
    @Value("${http.client.ssl.trust-store}")
    private Resource trustStore;

    @Value("${http.client.ssl.trust-store-password}")
    private char[] trustStorePassword;

    @Value("${http.client.maxPoolSize}")
    private Integer maxPoolSize;


    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory(httpClient());
    }

    @Bean
    public HttpClient httpClient() {

        // Trust own CA and all child certs
        Registry<ConnectionSocketFactory> socketFactoryRegistry = null;
        try {
            SSLContext sslContext = SSLContexts
                    .custom()
                    .loadTrustMaterial(trustStore.getFile(),
                            trustStorePassword)
                    .build();

            // Since only our own certs are trusted, hostname verification is probably safe to bypass
            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext,
                    new HostnameVerifier() {

                        @Override
                        public boolean verify(final String hostname,
                                final SSLSession session) {
                            return true;
                        }
            });

            socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslSocketFactory)
                    .build();           

        } catch (Exception e) {
            //TODO: handle exceptions
            e.printStackTrace();
        }

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connectionManager.setMaxTotal(maxPoolSize);
        // This client is for internal connections so only one route is expected
        connectionManager.setDefaultMaxPerRoute(maxPoolSize);
        return HttpClientBuilder.create()
                .setConnectionManager(connectionManager)
                .disableCookieManagement()
                .disableAuthCaching()
                .build();
    }

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(httpRequestFactory());
        return restTemplate;
    }    
}

然后,每当需要时,您都可以使用此自定义 Rest 客户端,例如:

@Autowired
private RestTemplate restTemplate;

restTemplate.getForEntity(...)

这假设您正在尝试连接到一个 Rest 端点,但您也可以将上述 HttpClient bean 用于任何其他用途。

2
我点赞了这个回答,因为我编写了一小段代码来证明 server.ssl.trust-store 是用于双向 TLS(如果设置了 server.ssl.client-auth 则会产生影响),而不是用于出站连接。这个回答可以被接受为 stackoverflow 的答案。我还想在这里指出,文档已经足够详细了:https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html - csikos.balint

5

虽然我评论得晚了,但我用这种方法来完成工作。当我运行我的Spring应用程序时,通过-Dspring.config.location=file:/location-to-file/config-server-vault-application.yml提供应用yaml文件,其中包含了所有的属性。

config-server-vault-application.yml
***********************************
server:
  port: 8888
  ssl:
    trust-store: /trust-store/config-server-trust-store.jks
    trust-store-password: config-server
    trust-store-type: pkcs12

************************************
Java Code
************************************
@SpringBootApplication
public class ConfigServerApplication {

 public static void main(String[] args) throws IOException {
    setUpTrustStoreForApplication();
    SpringApplication.run(ConfigServerApplication.class, args);
 }

 private static void setUpTrustStoreForApplication() throws IOException {
    YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
    List<PropertySource<?>> applicationYamlPropertySource = loader.load(
            "config-application-properties", new UrlResource(System.getProperty("spring.config.location")));
    Map<String, Object> source = ((MapPropertySource) applicationYamlPropertySource.get(0)).getSource();
    System.setProperty("javax.net.ssl.trustStore", source.get("server.ssl.trust-store").toString());
    System.setProperty("javax.net.ssl.trustStorePassword", source.get("server.ssl.trust-store-password").toString());
  }
}

我有一个带有@Configuration注释的单独的SSLConfig文件。但它就是不起作用。这是唯一一个让我的代码工作的答案。点赞!! - jkasper

4
如果您将Spring Boot应用程序作为Linux服务执行(例如init.d脚本或类似脚本),则还有以下选项: 创建名为yourApplication.conf的文件,并将其放置在可执行的war/jar文件旁边。它的内容应该类似于这样:
JAVA_OPTS="
-Djavax.net.ssl.trustStore=path-to-your-trustStore-file
-Djavax.net.ssl.trustStorePassword=yourCrazyPassword
"

2

这里是我对Oleksandr Shpota的答案进行扩展的版本,包括导入。包org.apache.http.*可以在org.apache.httpcomponents:httpclient中找到。我已经对更改进行了注释:

import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Value("${http.client.ssl.key-store}")
private Resource keyStore;

@Value("${http.client.ssl.trust-store}")
private Resource trustStore;

// I use the same pw for both keystores:
@Value("${http.client.ssl.trust-store-password}")
private String keyStorePassword;

// wasn't able to provide this as a @Bean...:
private RestTemplate getRestTemplate() {
  try {
    SSLContext sslContext = SSLContexts.custom()
        // keystore wasn't within the question's scope, yet it might be handy:
        .loadKeyMaterial(
            keyStore.getFile(),
            keyStorePassword.toCharArray(),
            keyStorePassword.toCharArray())
        .loadTrustMaterial(
            trustStore.getURL(),
            keyStorePassword.toCharArray(),
            // use this for self-signed certificates only:
            new TrustSelfSignedStrategy())
        .build();

    HttpClient httpClient = HttpClients.custom()
        // use NoopHostnameVerifier with caution, see https://dev59.com/UV_Va4cB1Zd3GeqPRDfA#22901289
        .setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()))
        .build();

    return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
  } catch (IOException | GeneralSecurityException e) {
    throw new RuntimeException(e);
  }
}

-3
如果你在使用Spring,尝试添加所需的属性(使用需要的属性),它应该适用于整个JVM。
javax:
  net:
    ssl:
      key-store-password: ${KEYSTORE_SECRET}
      key-store-type: PKCS12
      trust-store-password: ${TRUSTSTORE_SECRET}
      trust-store-type: PKCS12

-5
在微服务基础架构中(我知道这并不适用于问题;)你不能使用以下内容:
server:
  ssl:
    trust-store: path-to-truststore...
    trust-store-password: my-secret-password...

相反,可以配置带有负载均衡器的功能表:

ribbon: 
  TrustStore: keystore.jks
  TrustStorePassword : example
  ReadTimeout: 60000
  IsSecure: true
  MaxAutoRetries: 1

在这里https://github.com/rajaramkushwaha/https-zuul-proxy-spring-boot-app,您可以找到一个可用的示例。之前也有关于此的Github讨论,但我再也找不到了。


2
被踩了。即使在微服务基础架构中,仍有情况是你绝对需要配置信任存储的。例如,如果你的服务访问下游由你组织内部CA签名的HTTPS端点。 - sworisbreathing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接