问题
I have a Spring Boot application that calls a remote service.
This remote web service provided me a p12 file that should authenticate my application.
How do I configure my feign client to use the p12 certificate ?
I've tried settings these properties:
-Djavax.net.ssl.keyStore=path_to_cert.p12 -Djavax.net.ssl.keyStorePassword=xxx -Djavax.net.ssl.keyStoreType=PKCS12
But it doesn't change anything, I still get this error:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
回答1:
I could finally manage to do it with a lot of blind trial and error.
The problem is, by default, the feign builder builds feign clients with null SSLSocketFactory:
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);
So, I had to define this bean in a @Configuration:
@Bean
@Profile({"prod", "docker"})
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new Client.Default(getSSLSocketFactory(), null));
with this method: (can't remember source)
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();
}
Now, it works for me, I debugged though the feign client calls and the sslSocketFactory is correctly passed to the underlying connection.
回答2:
In case you wish to achieve the above effect programmatically without using keytool, you can do the following:
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)
}
}
}
The FeignClient interface that is using the configuration has to load this specifically
@FeignClient(name = "client", configuration = [CustomFeignConfiguration::class], url = "\${url}")
interface Client {
....
....
}
The SSLContexts library can only use p12 certificates and we have to convert the certificates and keys in PEM format to the P12 format.
Create a p12 certificate from your PEM certificate and key using the following SSL command:
openssl pkcs12 -export -inkey domain.key -in domain.crt -out domain.p12
Please record the password that you enter after you run this command.
Convert this p12 certificate to a base64 string using the following command
base64 domain.p12 > domain.p12.base64
Convert this multiline string to a single line string using the following command:
tr -d "\n\r" < domain.p12.base64 > domain.p12.base64.singleline
Use the single line string from this command and the password that you recorded earlier in your application.properties.
来源:https://stackoverflow.com/questions/58059632/how-to-use-p12-client-certificate-with-spring-feign-client