如何将 p12 客户端证书与 spring 假装客户端一起使用



我有一个调用远程服务的 Spring Boot 应用程序。

此远程 Web 服务为我提供了一个 p12 文件,该文件应验证我的应用程序。

如何配置我的假客户端以使用 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

我终于可以通过大量的盲目试验和错误来做到这一点。

问题是,默认情况下,假装构建器使用空SSLSocketFactory构建假客户端:

org.springframework.cloud.openfeign.FeignClientsConfiguration#feignBuilder

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}

装。Feign.Builder

public static class Builder {
// ...
private Client client = new Client.Default(null, null);

所以,我不得不用@Configuration来定义这个豆子:

@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 "nr" < domain.p12.base64 > domain.p12.base64.singleline

使用此命令中的单行字符串和您之前在application.properties中记录的密码。

与@BiAiB的回答相反,万一有人在 2023 年面临同样的问题。我将客户端配置为假装配置中的 Bean,并在那里设置 SSL 套接字工厂详细信息。

格拉德尔进口:

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.6'
implementation 'io.github.openfeign:feign-httpclient:12.3'

假装配置中的客户端 Bean:

@Bean
public Client feignClient() throws Exception {
log.info("Configuring SSL Context for Feign Client");
return new Client.Default(createSSLContext(), SSLConnectionSocketFactory.getDefaultHostnameVerifier());
}

并从资源文件创建了 SSL 套接字工厂,如下所示:

private SSLSocketFactory createSSLContext() throws Exception {
String trustStorePath = "classpath:cacerts"
String keyStorePath = "classpath:client-key.pfx"
log.info("Trust Store for Feign Client: " + trustStorePath);
log.info("Key Store for Feign Client: " + keyStorePath);
KeyStore keyStore = KeyStore.getInstance("PKCS12"); // PKCS12 for PFX files. Change this to 'JKS' if you are using java keystore
keyStore.load(new FileInputStream(ResourceUtils.getFile(keyStorePath)), keyStorePassword.toCharArray());
SSLContext context = SSLContextBuilder.create()
.loadTrustMaterial(ResourceUtils.getFile(trustStorePath), trustStorePassword.toCharArray())
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build();
return context.getSocketFactory();
}

最新更新