为了以X509用户身份验证使用反应性流连接到mongodb,mongodb驱动程序强制设置两个jvm属性:javax.net.ssl.keyStore
javax.net.ssl.keyStorePassword
https://mongodb.github.io/mongo-java-driver/4.0/driver-reactive/tutorials/ssl/。
我只能够在应用程序启动之前设置属性并使其工作
System.setProperty("javax.net.ssl.keyStore", "path");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SpringApplication.run(ChgQuerySvcApplication.class, args);
但是,如果我试图在扩展AbstractReactiveMongoConfiguration
的类中设置这些属性,它就不会恢复。
@Configuration
public class ReactiveMongoConfiguration extends AbstractReactiveMongoConfiguration {
@Autowired
Environment environment;
@Value("${mypassword}")
private String keyStorePassword;
@Override
public MongoClient reactiveMongoClient() {
MongoProperties properties = new MongoProperties();
properties.setDatabase("somdedb");
String uri = "mongodb+srv://CN=username@clusteraddress/somedb?authSource=%24external&authMechanism=MONGODB-X509&retryWrites=true&w=majority";
properties.setUri(uri);
ReactiveMongoClientFactory factory = new ReactiveMongoClientFactory(properties, environment, null);
System.setProperty("javax.net.ssl.keyStore", "path to key store");
System.setProperty("javax.net.ssl.keyStorePassword", "password"); // possibly replace with keyStorePassword
MongoCredential credential = MongoCredential.createMongoX509Credential("CN=username"); // redundant, I know
MongoClientSettings settings = MongoClientSettings.builder()
.applyToSslSettings(builder -> builder
.applySettings(SslSettings.builder().enabled(true).invalidHostNameAllowed(true).build()))
.credential(credential).build();
return factory.createMongoClient(settings);
}
}
我使用的spring依赖项(Version2.3.2.RELEASE):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
当我尝试连接时,我会得到以下异常:
{"@timestamp":"2020-08-07T17:34:18.346-05:00","logger_name":"org.mongodb.driver.client","thread_name":"async-channel-group-0-handler-executor","severity":"ERROR","trace":"","span":"","parent":"","message":"Calling onError threw an exception","stack_trace":"com.mongodb.MongoCommandException: Command failed with error 18 (AuthenticationFailed): 'No verified subject name available from client' on server servername:27017. The full response is {\"operationTime\": {\"$timestamp\": {\"t\": 1596839653, \"i\": 1}}, \"ok\": 0.0, \"errmsg\": \"No verified subject name available from client\", \"code\": 18, \"codeName\": \"AuthenticationFailed\", \"$clusterTime\": {\"clusterTime\": {\"$timestamp\": {\"t\": 1596839653, \"i\": 1}}, \"signature\": {\"hash\": {\"$binary\": {\"base64\": \"4IS/JaRasdauyWO9aXVOcaHm2s+3KzKg=\", \"subType\": \"00\"}}, \"keyId\": 123234}}}\r\n\tat com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:175)\r\n\tat com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:389)\r\n\t... 13 common frames omitted\r\nWrapped by: com.mongodb.MongoSecurityException: Exception authenticating\r\n\tat com.mongodb.internal.connection.X509Authenticator.translat...\r\n"}
{"@timestamp":"2020-08-07T17:34:18.347-05:00","logger_name":"org.mongodb.driver.client","thread_name":"async-channel-group-0-handler-executor","severity":"ERROR","trace":"","span":"","parent":"","message":"Callback onResult call produced an error","stack_trace":"com.mongodb.MongoCommandException: Command failed with error 18 (AuthenticationFailed): 'No verified subject name available from client' on server servername:27017. The full response is {\"operationTime\": {\"$timestamp\": {\"t\": 1596839653, \"i\": 1}}, \"ok\": 0.0, \"errmsg\": \"No verified subject name available from client\", \"code\": 18, \"codeName\": \"AuthenticationFailed\", \"$clusterTime\": {\"clusterTime\": {\"$timestamp\": {\"t\": 1596839653, \"i\": 1}}, \"signature\": {\"hash\": {\"$binary\": {\"base64\": \"4IS/asdJaRuyaWO9XVOcaHm2s+3KzKg=\", \"subType\": \"00\"}}, \"keyId\": 123234}}}\r\n\tat com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:175)\r\n\tat com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:389)\r\n\t... 13 common frames omitted\r\nWrapped by: com.mongodb.MongoSecurityException: Exception authenticating\r\n\tat com.mongodb.internal.connection.X509Authenticator.translat...\r\n"}
我尝试这样做的原因是,我可以通过设置密码,而不是硬编码或作为JVM参数传递。是否有一种动态设置这些属性的方法?
发布于 2020-08-19 15:34:13
tl;博士
在创建一个SSLContext连接之前,我们能够使用springboot的MongoClientSettingsBuilderCustomizer向MongoDB连接工厂提供一个新的MongoDB,基本上覆盖了JVM中可用的默认SSLContext
详细说明:
MongoDB的Java驱动程序完全依赖于JRE中的SSLContext
,因此无法通过连接字符串等方式设置它。我通过MongoDB支持确认了这一点。因为我们利用了spring-boot-starter-data-mongodb-reactive
。我们能够使用spring提供的一些自定义程序。下面是我们解决这个问题的方法:
我们创建了自定义程序的bean:
@Configuration
@RequiredArgsConstructor
public class MongoX509CredentialClientSettingsBuilderCustomizer implements MongoClientSettingsBuilderCustomizer {
private static final String MONGO_KEY_ENTRY_ALIAS = "mongo-client-key";
private static final JcaX509CertificateConverter X509_CERTIFICATE_CONVERTER = new JcaX509CertificateConverter();
private static final JcaPEMKeyConverter PEM_KEY_CONVERTER = new JcaPEMKeyConverter();
private final MongoX509Properties properties;
/**
* Only customizes the {@link SslSettings} for use with X.509 Certificate
* Authentication
*/
@Override
public void customize(Builder clientSettingsBuilder) {
// @formatter:off
clientSettingsBuilder
.applyToSslSettings(builder -> builder
.applySettings(SslSettings
.builder()
.context(sslContext())
.enabled(true)
.build()))
.credential(MongoCredential.createMongoX509Credential());
// @formatter:on
}
/**
* Creates an {@link SSLContext} that can connect to any endpoint exposing a
* valid well known CA by the JRE. And uses a dynamic array of
* {@link KeyManager} that contains the {@link X509Certificate} and
* {@link PrivateKey} configured for use with a MongoDB instance.
*
* @return
*/
@SneakyThrows
public SSLContext sslContext() {
SSLContext sslContext = SSLContext.getInstance(properties.getTlsVersion());
sslContext.init(keyManagers(x509Certificate(), privateKey()), trustManagers(), null);
return sslContext;
}
/**
* Creates an array of {@link TrustManager} containing the default set of
* trusted certificate authorities. This is required to make a TLS connection to
* the MongoDB instance. If MongoDB is Atlas then the CA is Let's Encrypt and
* should already be trusted so copy that over to the SSLContext that we are
* creating.
*
* @return an array of {@link TrustManager} initialized with the default trust
* managers.
*/
@SneakyThrows
private TrustManager[] trustManagers() {
TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here init the trustManagerFactory with the default trust store.
defaultTrustManagerFactory.init((KeyStore) null);
// only need the default trust managers if the CA for mongo is already in the
// default trust store for the JVM (e.g. via bosh managed trust store or via the
return defaultTrustManagerFactory.getTrustManagers();
}
/**
* Creates an array of {@link KeyManager} containing the certificate and
* privateKey provided in a in memory only {@link KeyStore} with alias
* {@link #MONGO_KEY_ENTRY_ALIAS} used for x509 authentication. This is a dymaic
* key manager containing the private key and certificate in the store for use
* by the {@link SSLContext} that this class creates.
*
* @param certificate
* @param privateKey
* @return an array of {@link KeyManager} initialized with the in memory
* {@link KeyStore}.
*/
@SneakyThrows
private KeyManager[] keyManagers(X509Certificate certificate, PrivateKey privateKey) {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null); // You don't need the KeyStore instance to come from a file.
keyStore.setKeyEntry(MONGO_KEY_ENTRY_ALIAS, privateKey, "".toCharArray(), new Certificate[] { certificate });
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "".toCharArray());
return keyManagerFactory.getKeyManagers();
}
/**
* Parses the PEM Encoded <b> mongo.x509.private-key </b> property to a
* {@link PrivateKey}.
*
* @return
*/
@SneakyThrows
private PrivateKey privateKey() {
try (PEMParser parser = new PEMParser(new StringReader(properties.getPrivateKey()))) {
return PEM_KEY_CONVERTER.getPrivateKey(PrivateKeyInfo.class.cast(parser.readObject()));
}
}
/**
* Parses the PEM Encoded <b>mongo.x509.certificate</b> property to
* {@link X509Certificate}.
*
* @return
*/
@SneakyThrows
private X509Certificate x509Certificate() {
try (PEMParser parser = new PEMParser(new StringReader(properties.getCertificate()))) {
return X509_CERTIFICATE_CONVERTER.getCertificate(X509CertificateHolder.class.cast(parser.readObject()));
}
}
}
因此,证书和密钥是通过MongoX509Properties注入的,后者从信用中心加载属性。这方面的另一个关键是如何解析证书。为此,我们使用了依赖项:
<!-- Bouncy Castle for parsing PEM encoded private key and certificate -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.65</version>
</dependency>
https://stackoverflow.com/questions/63310106
复制相似问题