如何在Spring Boot应用程序中使用SSL证书调用远程API。

huangapple go评论75阅读模式
英文:

How to use SSL Certificates in Spring Boot application to call remote API

问题

这是一个让我花了相当长时间才弄明白的话题。信息零散地分布在各个地方,必须将所有内容整合在一起。我希望通过这篇文章,能帮助其他人快速组装一个可行的解决方案。

我有一个 certificate.pemkey.pem 文件。我需要在我的 Java 客户端中使用它们,通过 JERSEY 客户端访问远程 REST API。

到目前为止,我尝试过以下几种方法:

将 pem 文件转换为 JKS 格式。

openssl pkcs12 -export -in certificate.pem -inkey key.pem -out client.jks -passout pass:CLIENT -name myClient

添加到信任库

keytool -importkeystore -destkeystore truststore.jks -deststoretype JKS -deststorepass CLIENT -srckeystore pathtoJKS://client.jks -srcstorepass CLIENT -srcstoretype JKS

添加以下属性

-Djavax.net.ssl.keyStorePassword=CLIENT
-Djavax.net.ssl.trustStore=truststore.jks
-Djavax.net.ssl.trustStorePassword=CLIENT```

但由于某种原因,远程 API 仍然认为我没有发送正确的证书。

有人可以帮助我找出这里缺少什么吗?

**编辑:-** 添加了 HTTP 客户端的代码。
```java
String keyStorePath = "client.jks"; // 文件位于 main/resources 文件夹中
String trustStorePath = "truststore.jks"; // 文件位于 main/resources 文件夹中

char[] keyStorePassword = "CLIENT".toCharArray();
char[] trustStorePassword = "CLIENT".toCharArray();

KeyStore keyStore = KeyStore.getInstance("JKS");
KeyStore trustStore = KeyStore.getInstance("JKS");

try (InputStream keyStoreInputStream = Application.class.getClassLoader().getResourceAsStream(keyStorePath);
     InputStream trustStoreInputStream = Application.class.getClassLoader().getResourceAsStream(trustStorePath)) {

    Objects.requireNonNull(keyStoreInputStream);
    Objects.requireNonNull(trustStoreInputStream);

    keyStore.load(keyStoreInputStream, keyStorePassword);
    trustStore.load(trustStoreInputStream, trustStorePassword);
}

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(keyManagers, trustManagers, new java.security.SecureRandom());

Client client = ClientBuilder.newBuilder()
    .sslContext(sslcontext)
    .hostnameVerifier(new TrustAllHostNameVerifier())
    .build();

如果需要进一步帮助,请随时提问。

英文:

This is a topic that has taken me quite some time to figure out. There are bits and pieces of information scattered and one has to put everything together. I was hoping that with this post I could help others quickly assemble a working solution.

I have a certificate.pem, key.pem. I need to use them in my Java client to access a remote REST API using JERSEY client.

Things I tried so far:

Converted pem files into JKS.

openssl pkcs12 -export -in certificate.pem -inkey key.pem -out client.jks -passout pass:CLIENT -name myClient

Added to truststore

keytool -importkeystore -destkeystore truststore.jks -deststoretype JKS -deststorepass CLIENT -srckeystore pathtoJKS://client.jks -srcstorepass CLIENT -srcstoretype JKS

Added properties as below

-Djavax.net.ssl.keyStore=client.jks
-Djavax.net.ssl.keyStorePassword=CLIENT
-Djavax.net.ssl.trustStore=truststore.jks
-Djavax.net.ssl.trustStorePassword=CLIENT

but for some reason the remote API still thinks like, I'm not sending the correct certificates.

Can someone help me what's missing here?

EDIT :- Added the code of the HTTP client.

String keyStorePath = "client.jks"; // File is present in main/resources folder
String trustStorePath = "truststore.jks"; // File is present in main/resources folder

char[] keyStorePassword = "CLIENT".toCharArray();
char[] trustStorePassword = "CLIENT".toCharArray();

KeyStore keyStore = KeyStore.getInstance("JKS");
KeyStore trustStore = KeyStore.getInstance("JKS");

try(InputStream keyStoreInputStream = Application.class.getClassLoader().getResourceAsStream(keyStorePath);
    InputStream trustStoreInputStream = Application.class.getClassLoader().getResourceAsStream(trustStorePath)) {

    Objects.requireNonNull(keyStoreInputStream);
    Objects.requireNonNull(trustStoreInputStream);

    keyStore.load(keyStoreInputStream, keyStorePassword);
    trustStore.load(trustStoreInputStream, trustStorePassword);
}

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(keyManagers,trustManagers, new java.security.SecureRandom());

Client client = ClientBuilder.newBuilder()
    .sslContext(sslcontext)
    .hostnameVerifier(new TrustAllHostNameVerifier())
    .build();

答案1

得分: 1

我不太确定以那种方式添加密钥库/信任库属性是否会起作用。在创建实例时,我习惯直接配置客户端配置,这样肯定会起作用。

以下是Jersey SSL客户端配置示例:

SSLFactory sslFactory = SSLFactory.builder()
    .withIdentityMaterial("client.jks", "CLIENT".toCharArray())
    .withTrustMaterial("truststore.jks", "CLIENT".toCharArray())
    .build();

Client client = ClientBuilder.newBuilder()
    .sslContext(sslFactory.getSslContext())
    .hostnameVerifier(sslFactory.getHostnameVerifier())
    .build();

Jersey需要配置好的SSLContext实例。上面的示例使用了来自SSLContext Kickstart的SSLFactory,这是由我维护的。还有其他创建SSLContext实例的方法,可以在这里查看我所知道的所有可能方法:https://stackoverflow.com/questions/61868096/mutual-authentication-in-scala-with-akka/61945168#61945168

你可以尝试上面的代码片段,并在这里提供结果吗?

英文:

I am not quite sure if adding the keystore/truststore properties in that way will work. I am used to configuring the client configuration directly while creating an instance, and that works for sure.

Below is an example Jersey SSL Client configuration:

SSLFactory sslFactory = SSLFactory.builder()
    .withIdentityMaterial("client.jks", "CLIENT".toCharArray())
    .withTrustMaterial("truststore.jks", "CLIENT".toCharArray())
    .build();

Client client = ClientBuilder.newBuilder()
    .sslContext(sslFactory.getSslContext())
    .hostnameVerifier(sslFactory.getHostnameVerifier())
    .build();

Jersey requires a configured instance of SSLContext. The above example uses SSLFactory from SSLContext Kickstart, which is maintained by me. There are also other ways to create an instance of SSLContext, see here for all the possible ways I know: https://stackoverflow.com/questions/61868096/mutual-authentication-in-scala-with-akka/61945168#61945168

Could you try the above snippet and drop here the results of it?

答案2

得分: 0

请与我们分享您的源代码,因为跟踪您目前面临的问题可能会有问题。乍一看,您的配置文件是正确的。但是,您可以进行一些小的改进:

  • 确保在运行时定义了 javax.net.ssl
  • 尝试指定绝对路径
英文:

Please share your source code with us, because its problematic to trace the problem that you are currently facing. At first glance, your configuration files are good. However, there are some small improvements you can do:

  • Make sure that javax.net.ssl are defined during runtime
  • Try to specify an absolute path

huangapple
  • 本文由 发表于 2020年8月25日 02:56:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/63567098.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定