首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >spring-boot-starter data-mongodb-从application.yml设置keystore密码以供使用X509连接

spring-boot-starter data-mongodb-从application.yml设置keystore密码以供使用X509连接
EN

Stack Overflow用户
提问于 2020-08-07 22:56:52
回答 2查看 1.5K关注 0票数 0

为了以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/

我只能够在应用程序启动之前设置属性并使其工作

代码语言:javascript
运行
复制
System.setProperty("javax.net.ssl.keyStore", "path");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SpringApplication.run(ChgQuerySvcApplication.class, args);

但是,如果我试图在扩展AbstractReactiveMongoConfiguration的类中设置这些属性,它就不会恢复。

代码语言:javascript
运行
复制
@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):

代码语言:javascript
运行
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

当我尝试连接时,我会得到以下异常:

代码语言:javascript
运行
复制
{"@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参数传递。是否有一种动态设置这些属性的方法?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 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:

代码语言:javascript
运行
复制
@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注入的,后者从信用中心加载属性。这方面的另一个关键是如何解析证书。为此,我们使用了依赖项:

代码语言:javascript
运行
复制
<!-- Bouncy Castle for parsing PEM encoded private key and certificate -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.65</version>
</dependency>
票数 0
EN

Stack Overflow用户

发布于 2020-08-10 20:49:09

x.509身份验证所需的所有选项都应该在最近驱动程序中的连接字符串中指定。看这里用于各种语言中的示例。

  1. 研究连接字符串文档
  2. 构造包含所有选项的连接字符串。
  3. 使用此连接字符串可以使用mongo shell连接到部署。不要使用命令行参数传递任何选项,只使用连接字符串.
  4. 在驱动程序中使用相同的连接字符串。

若要解决身份验证错误,请阅读服务器日志

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63310106

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档