让 Feign 客户端从自定义 属性 获取信任库
Make Feign client to take truststore from custom property
我们应用程序中的 Feign 客户端正在与自签名服务器通信。我们能够使用 属性 javax.net.ssl.trustStore
系统 属性 让 Feign 客户端使用自定义信任库。
但是因为我的应用程序还与标准的 CA 认证站点通信,所以不应覆盖默认的信任库。
如何在不使用 javax.net.ssl.trustStore
系统 属性 的情况下使用自定义信任库?或者我怎么能让我的 Feign 客户端使用来自 属性 的信任库而不是标准 javax.net.ssl.trustStore
系统 属性?
我最终使用以下代码手工制作了我自己的 SSLSocketFactory
实例,并将其传递给我的 Feign 客户端,
/**
* Gets the {@link SSLSocketFactory} instance for the client communication
* using the given truststore file and password.
*
* Since the instance is used as client, this is instantiated with empty
* keystore and the truststore represented by the given truststore file.
*
*
* @param theTrustStoreFile
* The complete file path of the truststore.
* @return {@link SSLSocketFactory} instance that internally uses the given
* truststore.
* @throws Exception
* When there is an error in the creating the
* {@link SSLSocketFactory} instance.
*/
public static SSLSocketFactory getClientSSLSocketFactory(File theTrustStoreFile)
throws Exception
{
// This supports TLSv1.2
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream file = getFileInputStream(theTrustStoreFile);
kStore.load(file, null);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(kStore);
sslContext.init(new KeyManager[] {}, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
/**
* Reads the file into {@link FileInputStream} instance.
*
* @param file
* The file to be read.
* @return {@link FileInputStream} that represents the file content/
* @throws Exception
* When there is any error in reading the file.
*/
private static FileInputStream getFileInputStream(final File file) throws Exception
{
return AccessController.doPrivileged(new PrivilegedExceptionAction<FileInputStream>()
{
@Override
public FileInputStream run() throws Exception
{
try
{
if (file.exists())
{
return new FileInputStream(file);
} else
{
return null;
}
} catch (FileNotFoundException e)
{
// couldn't find it, oh well.
return null;
}
}
});
}
当我实例化我的客户端时,我会这样做,
Feign.builder().client(getClientSSLSocketFactory(trustFile),null)...
此 gist contains 示例代码及其用法。
这就是我将 FeignClient 与 keystore
和 truststore
一起使用的方式
FeignClient 配置
@Configuration
public class TestClientConfig {
@Bean
public Client feignClient() {
Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
return trustSSLSockets;
}
private SSLSocketFactory getSSLSocketFactory() {
try {
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//Do your validations
return true;
}
};
String allPassword = "123456";
SSLContext sslContext = SSLContextBuilder
.create()
// .loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.p12"), allPassword.toCharArray(), allPassword.toCharArray())
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"), allPassword.toCharArray(), allPassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword.toCharArray())
.build();
return sslContext.getSocketFactory();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
}
界面
@FeignClient(name = "Test", url = "https://localhost:8443",configuration=TestClientConfig.class)
public interface TestClient {
@RequestMapping(method = RequestMethod.GET,value = "/hello")
String getHello();
}
多亏了 Kannan 和 Niraj 的回答,我成功地将我的假客户端配置为使用 customSSLSocketFactory。
我的用例:我有一个 API 需要将 SSL 证书添加到我本地机器上的 JAVA cacerts 才能获取它。但是,在云服务器上部署后,我无法添加此证书。因此,我需要配置我的 Feign 客户端以从我的应用程序本身接收自定义信任库。
这是我的版本
使用添加的证书创建您自己的信任库
keytool -import -file <filepath-of-certificate> -alias <certificate-alias-name> -keystore <truststore-alias-name>
系统会提示输入密码,自行设置密码并记住
对于这种情况,Truststore 不需要任何扩展
将此信任库文件复制到 spring 启动应用程序的资源文件夹中
在 application.properties 或 application.yml 文件中为您的信任库设置 class 路径和密码。我正在使用 application.yml 所以示例如下
例如
example-service:
server: https://example.api.com
trustStore: classpath:<Your truststore name>
trustStorePassword: yourpassword
也可以跳过第3步,直接在下方传入truststore和密码
- 在CustomFeignConfig class中可以直接将truststore作为资源传入,直接将truststore inputstream传入sslsocketfactory。请注意,Bean 表示法对于覆盖默认的 feign 配置是必不可少的。
我的案例只需要一个信任库,所以我在 sslContext.init()
上传入了一个空的 new KeyManager[]{}
public class CustomFeignConfig {
@Bean
public Client feignClient(@Value("${example-service.trustStore}") Resource trustStoreResource,
@Value("${example-service.trustStorePassword}") String trustStorePassword) throws Exception {
try {
return new Client.Default(
sslSocketFactory(trustStoreResource.getInputStream(), trustStorePassword),
null);
} catch (Exception e) {
throw new Exception("Error in initializing feign client", e);
}
}
private static SSLSocketFactory sslSocketFactory(InputStream trustStoreStream, String trustStorePassword)
throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
TrustManagerFactory tmf = createTrustManager(trustStoreStream, trustStorePassword);
sslContext.init(new KeyManager[]{}, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
private static TrustManagerFactory createTrustManager(InputStream trustStoreStream, String trustStorePassword)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(trustStoreStream, trustStorePassword.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
return tmf;
}
}
- 仅将此自定义伪装配置应用于需要它的 api
@FeignClient(name = "example-service", url = "${example-service.server}", configuration = CustomFeignConfig.class)
public interface MyFeignClient {}
我们应用程序中的 Feign 客户端正在与自签名服务器通信。我们能够使用 属性 javax.net.ssl.trustStore
系统 属性 让 Feign 客户端使用自定义信任库。
但是因为我的应用程序还与标准的 CA 认证站点通信,所以不应覆盖默认的信任库。
如何在不使用 javax.net.ssl.trustStore
系统 属性 的情况下使用自定义信任库?或者我怎么能让我的 Feign 客户端使用来自 属性 的信任库而不是标准 javax.net.ssl.trustStore
系统 属性?
我最终使用以下代码手工制作了我自己的 SSLSocketFactory
实例,并将其传递给我的 Feign 客户端,
/**
* Gets the {@link SSLSocketFactory} instance for the client communication
* using the given truststore file and password.
*
* Since the instance is used as client, this is instantiated with empty
* keystore and the truststore represented by the given truststore file.
*
*
* @param theTrustStoreFile
* The complete file path of the truststore.
* @return {@link SSLSocketFactory} instance that internally uses the given
* truststore.
* @throws Exception
* When there is an error in the creating the
* {@link SSLSocketFactory} instance.
*/
public static SSLSocketFactory getClientSSLSocketFactory(File theTrustStoreFile)
throws Exception
{
// This supports TLSv1.2
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream file = getFileInputStream(theTrustStoreFile);
kStore.load(file, null);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(kStore);
sslContext.init(new KeyManager[] {}, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
/**
* Reads the file into {@link FileInputStream} instance.
*
* @param file
* The file to be read.
* @return {@link FileInputStream} that represents the file content/
* @throws Exception
* When there is any error in reading the file.
*/
private static FileInputStream getFileInputStream(final File file) throws Exception
{
return AccessController.doPrivileged(new PrivilegedExceptionAction<FileInputStream>()
{
@Override
public FileInputStream run() throws Exception
{
try
{
if (file.exists())
{
return new FileInputStream(file);
} else
{
return null;
}
} catch (FileNotFoundException e)
{
// couldn't find it, oh well.
return null;
}
}
});
}
当我实例化我的客户端时,我会这样做,
Feign.builder().client(getClientSSLSocketFactory(trustFile),null)...
此 gist contains 示例代码及其用法。
这就是我将 FeignClient 与 keystore
和 truststore
FeignClient 配置
@Configuration
public class TestClientConfig {
@Bean
public Client feignClient() {
Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
return trustSSLSockets;
}
private SSLSocketFactory getSSLSocketFactory() {
try {
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//Do your validations
return true;
}
};
String allPassword = "123456";
SSLContext sslContext = SSLContextBuilder
.create()
// .loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.p12"), allPassword.toCharArray(), allPassword.toCharArray())
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"), allPassword.toCharArray(), allPassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword.toCharArray())
.build();
return sslContext.getSocketFactory();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
}
界面
@FeignClient(name = "Test", url = "https://localhost:8443",configuration=TestClientConfig.class)
public interface TestClient {
@RequestMapping(method = RequestMethod.GET,value = "/hello")
String getHello();
}
多亏了 Kannan 和 Niraj 的回答,我成功地将我的假客户端配置为使用 customSSLSocketFactory。
我的用例:我有一个 API 需要将 SSL 证书添加到我本地机器上的 JAVA cacerts 才能获取它。但是,在云服务器上部署后,我无法添加此证书。因此,我需要配置我的 Feign 客户端以从我的应用程序本身接收自定义信任库。
这是我的版本
使用添加的证书创建您自己的信任库
keytool -import -file <filepath-of-certificate> -alias <certificate-alias-name> -keystore <truststore-alias-name>
系统会提示输入密码,自行设置密码并记住 对于这种情况,Truststore 不需要任何扩展将此信任库文件复制到 spring 启动应用程序的资源文件夹中
在 application.properties 或 application.yml 文件中为您的信任库设置 class 路径和密码。我正在使用 application.yml 所以示例如下
例如
example-service:
server: https://example.api.com
trustStore: classpath:<Your truststore name>
trustStorePassword: yourpassword
也可以跳过第3步,直接在下方传入truststore和密码
- 在CustomFeignConfig class中可以直接将truststore作为资源传入,直接将truststore inputstream传入sslsocketfactory。请注意,Bean 表示法对于覆盖默认的 feign 配置是必不可少的。 我的案例只需要一个信任库,所以我在 sslContext.init() 上传入了一个空的 new KeyManager[]{}
public class CustomFeignConfig {
@Bean
public Client feignClient(@Value("${example-service.trustStore}") Resource trustStoreResource,
@Value("${example-service.trustStorePassword}") String trustStorePassword) throws Exception {
try {
return new Client.Default(
sslSocketFactory(trustStoreResource.getInputStream(), trustStorePassword),
null);
} catch (Exception e) {
throw new Exception("Error in initializing feign client", e);
}
}
private static SSLSocketFactory sslSocketFactory(InputStream trustStoreStream, String trustStorePassword)
throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
TrustManagerFactory tmf = createTrustManager(trustStoreStream, trustStorePassword);
sslContext.init(new KeyManager[]{}, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
private static TrustManagerFactory createTrustManager(InputStream trustStoreStream, String trustStorePassword)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(trustStoreStream, trustStorePassword.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
return tmf;
}
}
- 仅将此自定义伪装配置应用于需要它的 api
@FeignClient(name = "example-service", url = "${example-service.server}", configuration = CustomFeignConfig.class)
public interface MyFeignClient {}