我正在尝试在 Spring 客户端和 Kafka 之间建立 ssl(mTLS) 连接。 我得到了两个文件:
-app.keystore;
-client.truststore.
当我用
/usr/bin/keytool -list -rfc -keystore app.keystore |grep "Keystore type"
检查文件时,
我发现类型是PKCS12。
我手动配置Kafka,因此我有一个具有Kafka属性的配置类。在配置中,我将属性“ssl.keystore.type”和“ssl.truststore.typ”设置为“PKCS12”。
文件位置在 application.properties 中设置,并使用 @Value 注释粘贴到配置中(请参阅下面的配置代码):
kafka.trust-store-location=classpath:ssl/client.truststore
kafka.key-store-location=classpath:ssl/app.keystore
物理文件放置在文件夹中: maven_module_name/src/main/resources/ssl/client.truststore maven_module_name/src/main/resources/ssl/app.keystore
当我启动应用程序时,出现错误:
Caused by: org.apache.kafka.common.KafkaException: Failed to load SSL keystore classpath:ssl/app.keystore of type PKCS12
Caused by: java.nio.file.NoSuchFileException: classpath:ssl/app.keystore
这是我的配置类:
@Configuration
public class KafkaProducerConfiguration {
...
@Value("${kafka.key-store-location}")
private String keyStoreLocation;
@Value("${kafka.truststore-location}")
private String trustStoreLocation;
@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
...
...
props.put("security.protocol", "SSL");
props.put("ssl.key.password", "password");
props.put("ssl.keystore.location", keyStoreLocation);
props.put("ssl.keystore.password", "password");
props.put("ssl.keystore.type", "PKCS12");
props.put("ssl.truststore.location", trustStoreLocation);
props.put("ssl.truststore.password", "password");
props.put("ssl.truststore.type", "PKCS12");
return props;
}
@Bean
public <T> ProducerFactory<Long, T> someProducerFactory() {
return new DefaultKafkaProducerFactory<>(producerConfigs());
}
@Bean
public <T> KafkaTemplate<Long, T> someKafkaTemplate() {
...
return template;
}
}
我还尝试按照此答案中的建议使用资源界面:
@Value("${kafka.key-store-location}")
private Resource keyStoreLocation;
@Value("${kafka.trust-store-location}")
private Resource trustStoreLocation;
当我尝试以这种方式获取绝对路径时:
props.put("ssl.keystore.location", keyStoreLocation.getFile().getAbsolutePath());
应用程序抛出异常:
java.io.FileNotFoundException: class path resource [ssl/app.keystore] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/app.jar!/BOOT-INF/classes!/ssl/app.keystore
请向我建议如何解决该问题。 谢谢。
看起来这是 Kafka 客户端中的一个“已知问题”。 它无法从类路径加载文件,只能从文件系统加载文件。因此,当您构建应用程序时,src/main/resources 下的所有内容都会打包在 jar 文件中,并且不能直接从磁盘读取。
Spring Boot 提出了一个
解决方法,如下:
FileCopyUtils.copy(new ClassPathResource("client.ks").getInputStream(),
new FileOutputStream("/tmp/client.ks"));
您的配置文件可以如下所示:
ssl.keystore.location=/tmp/client.ks
请遵循以下一般步骤:
import org.apache.kafka.common.config.AbstractConfig;
import org.apache.kafka.common.config.ConfigException;
import org.springframework.credhub.core.CredHubProperties;
import org.springframework.credhub.core.CredHubTemplate;
import org.springframework.credhub.support.CredentialDetails;
import org.springframework.credhub.support.CredentialName;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
public class CredHubKeyStoreProvider implements KeyStoreProvider {
private final CredHubTemplate credHubTemplate;
private final StringKeyGenerator keyGenerator = KeyGenerators.string();
private final Map<String, String> keyNameMapping;
public CredHubKeyStoreProvider(CredHubProperties credHubProperties, Map<String, String> keyNameMapping) {
this.credHubTemplate = new CredHubTemplate(credHubProperties);
this.keyNameMapping = keyNameMapping;
}
@Override
public KeyStore getKeyStore() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
keyStore.load(null, null);
for (Map.Entry<String, String> entry : keyNameMapping.entrySet()) {
String alias = entry.getKey();
String credHubName = entry.getValue();
CredentialDetails<?> credentialDetails = credHubTemplate.getByName(new CredentialName(credHubName));
if (credentialDetails != null) {
Object credentialValue = credentialDetails.getValue();
if (credentialValue instanceof byte[]) {
keyStore.setKeyEntry(alias, new javax.crypto.spec.SecretKeySpec((byte[]) credentialValue, "AES"), null, null);
} else if (credentialValue instanceof String) {
keyStore.setEntry(alias, new KeyStore.SecretKeyEntry(new javax.crypto.spec.SecretKeySpec(((String) credentialValue).getBytes(), "AES")), null);
}
}
}
return keyStore;
}
@Override
public BytesKeyGenerator getBytesKeyGenerator() {
return keyGenerator;
}
@Override
public StringKeyGenerator getStringKeyGenerator() {
return keyGenerator;
}
}
要将 Spring Boot Kafka 客户端配置为使用自定义 KeyStoreProvider,您可以在应用程序的配置中设置 security.provider.classes 属性,如下所示:
spring.kafka.security.provider.classes=com.example.CredHubKeyStoreProvider
在此示例中,com.example.CredHubKeyStoreProvider 是自定义 KeyStoreProvider 实现的完全限定名称。
不幸的是,
ssl.keystore.location
需要设置为可读文件,即使我们的KeyStore实现不使用它,因此我们将其设置为/dev/null,并将
ssl.keystore.password
设置为忽略。请注意,您可能还需要在应用程序配置中配置与 SSL 和安全相关的其他属性,具体取决于您的 Kafka 集群。
FileCopyUtils.copy(new ClassPathResource("truststore.jks").getInputStream(),
new FileOutputStream("truststore.jks"));
在应用程序 yml 中我添加了以下配置。
trust-store-location: file:truststore.jks
JFYI,我没有为 Kafka 创建任何配置。这一切都是由 Spring boot 自己完成的。
另外,让我添加导入内容,以免访客感到困惑。
import java.io.FileOutputStream;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;