如何在 spring feign client 中使用 p12 客户端证书
How to use p12 client certificate with spring feign client
我有一个 Spring 调用远程服务的启动应用程序。
此远程 Web 服务为我提供了一个 p12 文件,用于验证我的应用程序。
如何配置我的 feign 客户端以使用 p12 证书?
我试过设置这些属性:
-Djavax.net.ssl.keyStore=path_to_cert.p12 -Djavax.net.ssl.keyStorePassword=xxx -Djavax.net.ssl.keyStoreType=PKCS12
但它没有改变任何东西,我仍然得到这个错误:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
经过大量的盲目试错,我终于做到了。
问题是,默认情况下,feign 构建器使用 null SSLSocketFactory 构建 feign 客户端:
org.springframework.cloud.openfeign.FeignClientsConfiguration#feignBuilder:
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
feign.Feign.Builder:
public static class Builder {
// ...
private Client client = new Client.Default(null, null);
所以,我必须在@Configuration 中定义这个 bean:
@Bean
@Profile({"prod", "docker"})
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new Client.Default(getSSLSocketFactory(), null));
用这个方法:(不记得来源)
SSLSocketFactory getSSLSocketFactory() {
char[] allPassword = keyStorePassword.toCharArray();
SSLContext sslContext = null;
try {
sslContext = SSLContextBuilder
.create()
.setKeyStoreType(keyStoreType)
.loadKeyMaterial(ResourceUtils.getFile(keyStore), allPassword, allPassword)
.build();
} catch (Exception e) { /* *** */ }
return sslContext.getSocketFactory();
}
现在,它对我有用,我调试了假客户端调用并且 sslSocketFactory 正确传递到底层连接。
如果您希望在不使用 keytool 的情况下以编程方式实现上述效果,您可以执行以下操作:
class CustomFeignConfiguration {
private val log = Logger.getLogger(this.javaClass.name)
@Value("${client_p12_base64_encoded_string}")
private val clientP12: String = ""
@Value("${client_p12_password}")
private val clientP12Pass: String = ""
@Bean
fun feignClient(): Client {
val sslSocketFactory= getSSLSocketFactory()
log.info("CUSTOM FEIGN CLIENT CALLED")
return Client.Default(sslSocketFactory, DefaultHostnameVerifier())
}
private fun getSSLSocketFactory(): SSLSocketFactory {
val decoder = java.util.Base64.getDecoder()
val p12 = decoder.decode(clientP12)
val p12File = File("clientCer.p12")
p12File.writeBytes(p12)
try {
val sslContext = SSLContexts
.custom()
.loadKeyMaterial(p12File, clientP12Pass.toCharArray(), clientP12Pass.toCharArray())
.build()
return sslContext.socketFactory
} catch (exception: Exception) {
throw RuntimeException(exception)
}
}
}
使用配置的 FeignClient 接口必须专门加载此
@FeignClient(name = "client", configuration = [CustomFeignConfiguration::class], url = "${url}")
interface Client {
....
....
}
SSLContexts 库只能使用 p12 证书,我们必须将 PEM 格式的证书和密钥转换为 P12 格式。
使用以下 SSL 命令从 PEM 证书和密钥创建 p12 证书:
openssl pkcs12 -export -inkey domain.key -in domain.crt -out domain.p12
请记录您在运行此命令后输入的密码。
使用以下命令将此 p12 证书转换为 base64 字符串
base64 domain.p12 > domain.p12.base64
使用以下命令将此多行字符串转换为单行字符串:
tr -d "\n\r" < domain.p12.base64 > domain.p12.base64.singleline
使用此命令中的单行字符串和您之前在 application.properties.
中记录的密码
我有一个 Spring 调用远程服务的启动应用程序。
此远程 Web 服务为我提供了一个 p12 文件,用于验证我的应用程序。
如何配置我的 feign 客户端以使用 p12 证书?
我试过设置这些属性:
-Djavax.net.ssl.keyStore=path_to_cert.p12 -Djavax.net.ssl.keyStorePassword=xxx -Djavax.net.ssl.keyStoreType=PKCS12
但它没有改变任何东西,我仍然得到这个错误:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
经过大量的盲目试错,我终于做到了。
问题是,默认情况下,feign 构建器使用 null SSLSocketFactory 构建 feign 客户端:
org.springframework.cloud.openfeign.FeignClientsConfiguration#feignBuilder:
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
feign.Feign.Builder:
public static class Builder {
// ...
private Client client = new Client.Default(null, null);
所以,我必须在@Configuration 中定义这个 bean:
@Bean
@Profile({"prod", "docker"})
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new Client.Default(getSSLSocketFactory(), null));
用这个方法:(不记得来源)
SSLSocketFactory getSSLSocketFactory() {
char[] allPassword = keyStorePassword.toCharArray();
SSLContext sslContext = null;
try {
sslContext = SSLContextBuilder
.create()
.setKeyStoreType(keyStoreType)
.loadKeyMaterial(ResourceUtils.getFile(keyStore), allPassword, allPassword)
.build();
} catch (Exception e) { /* *** */ }
return sslContext.getSocketFactory();
}
现在,它对我有用,我调试了假客户端调用并且 sslSocketFactory 正确传递到底层连接。
如果您希望在不使用 keytool 的情况下以编程方式实现上述效果,您可以执行以下操作:
class CustomFeignConfiguration {
private val log = Logger.getLogger(this.javaClass.name)
@Value("${client_p12_base64_encoded_string}")
private val clientP12: String = ""
@Value("${client_p12_password}")
private val clientP12Pass: String = ""
@Bean
fun feignClient(): Client {
val sslSocketFactory= getSSLSocketFactory()
log.info("CUSTOM FEIGN CLIENT CALLED")
return Client.Default(sslSocketFactory, DefaultHostnameVerifier())
}
private fun getSSLSocketFactory(): SSLSocketFactory {
val decoder = java.util.Base64.getDecoder()
val p12 = decoder.decode(clientP12)
val p12File = File("clientCer.p12")
p12File.writeBytes(p12)
try {
val sslContext = SSLContexts
.custom()
.loadKeyMaterial(p12File, clientP12Pass.toCharArray(), clientP12Pass.toCharArray())
.build()
return sslContext.socketFactory
} catch (exception: Exception) {
throw RuntimeException(exception)
}
}
}
使用配置的 FeignClient 接口必须专门加载此
@FeignClient(name = "client", configuration = [CustomFeignConfiguration::class], url = "${url}")
interface Client {
....
....
}
SSLContexts 库只能使用 p12 证书,我们必须将 PEM 格式的证书和密钥转换为 P12 格式。
使用以下 SSL 命令从 PEM 证书和密钥创建 p12 证书:
openssl pkcs12 -export -inkey domain.key -in domain.crt -out domain.p12
请记录您在运行此命令后输入的密码。
使用以下命令将此 p12 证书转换为 base64 字符串
base64 domain.p12 > domain.p12.base64
使用以下命令将此多行字符串转换为单行字符串:
tr -d "\n\r" < domain.p12.base64 > domain.p12.base64.singleline
使用此命令中的单行字符串和您之前在 application.properties.
中记录的密码