skip to Main Content

I’m trying to make some modifications to our Spring config file, for connecting to mongo. Right now, we simply define the URL

<mongo:db-factory id="mongo_connection" client-uri="mongodb://server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test" />

I’m now setting up authentication on the mongo server, which would change the connection string to mongodb://username:password@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test. However, I can’t hardcode these into the XML; they will come from a different API on startup.

I know how to setup the URL in a seperate Java file:

public class MongoConstructor {

    String username = secret_username;
    String password = secret_password;
    String db = "db_name"
    String rs = "test"

    //uri="mongodb://server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test"

    public String getMongoConstructor() {
        return "mongodb://" + username + ":" + password + "@server_1:27000,server_2:27000,server_3:27000/" + db + "?replicaSet=" + rs;
    }
}

Is there anyway for me to create that value before the bean is registered, and pass that value into the config file? If not, is there anyway for me to override the config in the xml file later on?

EDIT: Having done more research, I’ve discovered plaveholders for Spring, which seems to be what I’m looking for. However, I’m still struggling. So far, I’ve made the change in the XML file:

<beans>
<mongo:db-factory id="devMongoConnection"
                          client-uri="mongodb://{username}:{password}@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test" />
</beans>

Since I can’t use a properties file to store the credential information, I’ve made a Java file to initialize the username and password:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecretImpl {

    Secret secret = Internal_API_For_Secrets();

    public SecretImpl(String username, String password) {
    }

    @Bean(name="username")
    public String username() {
        return secret.getUsername();
    }

    @Bean(name="password")
    public String password() {
        return secret.getUsername();
    }
}

What I’m struggling with is getting these secrets into the xml file. All the documentation I’ve seen so far seems to assume that I would use either xml or Java, not a mix. They also seem to assume that the password can be placed in a properties files as plain text.

2

Answers


  1. Probably the best way to go would be using SpEL expressions for accessing either system properties or system environment variables with your credentials configuration.

    The approach is described in the Spring documentation.

    For example, you can define system properties like this when running your program:

    -Dmongo_username=your_username -Dmongo_password=your_password
    

    and then reference those properties in your XML configuration like this:

    <mongo:db-factory id="mongo_connection" client-uri="mongodb://#{systemProperties['mongo_username']}:#{systemProperties['mongo_password']}@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test" />
    

    Or preferably, you can define the same information using environment variables. For example, in Linux or Unix based systems:

    export MONGO_USERNAME=your_username
    export MONGO_PASSWORD=your_password
    

    and use those variables like this:

    <mongo:db-factory id="mongo_connection" client-uri="mongodb://#{systemEnvironment['MONGO_USERNAME']}:#{systemEnvironment['MONGO_PASSWORD']}@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test" />
    

    I am not sure if it will work, because it highly depends on how you are obtaining the credentials, but you could define a bean corresponding to a slightly modified version of your SecretImpl class:

    public class SecretImpl {
    
        Secret secret = Internal_API_For_Secrets();
    
        public String getUsername() {
            return secret.getUsername();
        }
    
        public String getPassword() {
            return secret.getUsername();
        }
    }
    

    in your XML file and reference the corresponding properties in your Mongo configuration, using SpEL again:

    <bean id="secretImpl" class="your.pkg.SecretImpl" />
    
    <mongo:db-factory
      id="mongo_connection"
      client-uri="mongodb://#{secretImpl.username}:#{secretImpl.password}@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test" />
    

    Finally, you could try providing a modified version of the MongoDatabaseFactory for taking into account the API for obtaining the credentials. It is unclear in your question the version of the Spring Data library for Mongo that you are using, but assuming the most recent, the MongoDatabaseFactory abstraction is implemented in SimpleMongoClientDatabaseFactory. The proposed solution will then consist of defining a class that, for example, extends that class providing the necessary artifacts for dealing with your API for obtaining the required credentials, something like (please, excuse the simplicity of the code):

    public class MyCredentialVaultMongoClientDatabaseFactory
      extends SimpleMongoClientDatabaseFactory {
    
      // For instance, you can keep a reference to the class required
      // for obtaining your credentials for separation of concerns
      // or use this factory class itself for fetching the credentials
      // private SecretImpl secretImpl;
    
      public MyCredentialVaultMongoClientDatabaseFactory(String connectionStringPattern, SecretImpl secretImpl) {
        super(
          connectionStringPattern.format(
            secretImpl.getUsername(),
            secretImpl.getPassword()
          )
        );
      }
    }
    

    Your XML configuration could look similar to this (based for instance on this):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
        <bean id="secretImpl" class="your.pkg.SecretImpl">
            <!-- properties for accessing your vault -->
        </bean>
    
        <bean id="mongoDbFactory" class="your.pkg.MyCredentialVaultMongoClientDatabaseFactory">
            <constructor-arg name="connectionStringPattern" value="mongodb://%s:%s@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test"/>
            <constructor-arg name="secretImpl" ref="secretImpl"/>
        </bean>
    
        <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
            <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
            <property name="writeConcern" value="majority" />
        </bean>
    
    </beans>
    
    Login or Signup to reply.
  2. Actually it is not clear what Secret secret = Internal_API_For_Secrets(); does.

    But you could register your own properties on application startup in ConfigurableEnvironment

    First option

    You can achieve it the following way:

    Implement your own EnvironmentPostProcessor:

    public class MongoPropertiesEnvironmentPostProcessor implements EnvironmentPostProcessor {
    
        @Override
        public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
            // I don't know where you get these credentials here from, adapt this to your needs
            String user = "user"; 
            String password = "qwerty";
    
            String url = String.format("mongodb://%s:%s@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test",
                    user, password);
            Map<String, Object> mongoProperties = Map.of("mongo-url", url);
            environment.getPropertySources()
                    .addLast(new MapPropertySource("mongo-properties", mongoProperties));
        }
    }
    

    Register that post processor with spring.factories file. Create this file in META-INF directory. By maven conventions it should be main/java/resources/META-INF/spring.factories

    There you specify the class name with package

    org.springframework.boot.env.EnvironmentPostProcessor=com.example.config.MongoPropertiesEnvironmentPostProcessor
    

    So, during startup your property mongo-url will be registered in Environment and will be also accessible with the placeholder.

    From Java code:

    @Value("${mongo-url}")
    private String mongoUrl;
    

    In xml:

    <mongo:db-factory id="devMongoConnection" client-uri="${mongo-url}"/>
    

    Second option

    Another option if you want to inject some bean that makes a call to the external API:

    Declare an ApplicationListener<ContextRefreshedEvent>:

    @Service
    class MongoPropertyInjector implements ApplicationListener<ContextRefreshedEvent> {
    
        @Autowired
        private ConfigurableEnvironment configurableEnvironment;
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            // I don't know where you get these credentials here, adapt this to your needs, you can autowire a bean that makes a call to external API and make a call here
            String user = "user";
            String password = "qwerty";
    
            String url = String.format("mongodb://%s:%s@server_1:27000,server_2:27000,server_3:27000/db_name?replicaSet=test",
                    user, password);
            Map<String, Object> mongoProperties = Map.of(
                    "mongo-url", url
            );
            configurableEnvironment.getPropertySources()
                    .addLast(new MapPropertySource("mongo-properties", mongoProperties));
        }
    }
    

    After this you can use the placeholder ${mongo-url} both in your Java configs and in XML configs, just like in the example above.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search