DATAMONGO-789 - Support login via different authentication database.

MongoDbUtils now supports to perform the authentication against a dedicated authenticationDatabase - if no authenticationDatabase is given explicitly then the given regular database will be used. The authentication database can be configured via the authentication-dbname attribute of the db-factory element in xml config or by overriding the getAuthenticationDatabaseName() method of AbstractMongoConfiguration.

Original pull request: #92.
This commit is contained in:
Thomas Darimont
2013-11-04 13:29:26 +01:00
committed by Oliver Gierke
parent 332e5eb715
commit 7c0eee9e09
12 changed files with 178 additions and 38 deletions

View File

@@ -49,6 +49,7 @@ import com.mongodb.Mongo;
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Thomas Darimont
*/
@Configuration
public abstract class AbstractMongoConfiguration {
@@ -60,6 +61,16 @@ public abstract class AbstractMongoConfiguration {
*/
protected abstract String getDatabaseName();
/**
* Return the name of the authentication database to use. Defaults to {@literal null} and will turn into the value
* returned by {@link #getDatabaseName()} later on effectively.
*
* @return
*/
protected String getAuthenticationDatabaseName() {
return null;
}
/**
* Return the {@link Mongo} instance to connect to. Annotate with {@link Bean} in case you want to expose a
* {@link Mongo} instance to the {@link org.springframework.context.ApplicationContext}.
@@ -91,14 +102,7 @@ public abstract class AbstractMongoConfiguration {
*/
@Bean
public SimpleMongoDbFactory mongoDbFactory() throws Exception {
UserCredentials credentials = getUserCredentials();
if (credentials == null) {
return new SimpleMongoDbFactory(mongo(), getDatabaseName());
} else {
return new SimpleMongoDbFactory(mongo(), getDatabaseName(), credentials);
}
return new SimpleMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials(), getAuthenticationDatabaseName());
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 by the original author(s).
* Copyright 2011-2013 by the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@ import com.mongodb.MongoURI;
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
@@ -70,6 +71,7 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
String uri = element.getAttribute("uri");
String mongoRef = element.getAttribute("mongo-ref");
String dbname = element.getAttribute("dbname");
BeanDefinition userCredentials = getUserCredentialsBeanDefinition(element, parserContext);
// Common setup
@@ -92,12 +94,9 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser {
dbFactoryBuilder.addConstructorArgValue(registerMongoBeanDefinition(element, parserContext));
}
dbname = StringUtils.hasText(dbname) ? dbname : "db";
dbFactoryBuilder.addConstructorArgValue(dbname);
if (userCredentials != null) {
dbFactoryBuilder.addConstructorArgValue(userCredentials);
}
dbFactoryBuilder.addConstructorArgValue(StringUtils.hasText(dbname) ? dbname : "db");
dbFactoryBuilder.addConstructorArgValue(userCredentials);
dbFactoryBuilder.addConstructorArgValue(element.getAttribute("authentication-dbname"));
BeanDefinitionBuilder writeConcernPropertyEditorBuilder = getWriteConcernPropertyEditorBuilder();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2012 the original author or authors.
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import com.mongodb.Mongo;
* Mongo server administration exposed via JMX annotations
*
* @author Mark Pollack
* @author Thomas Darimont
*/
@ManagedResource(description = "Mongo Admin Operations")
public class MongoAdmin implements MongoAdminOperations {
@@ -34,6 +35,7 @@ public class MongoAdmin implements MongoAdminOperations {
private final Mongo mongo;
private String username;
private String password;
private String authenticationDatabaseName;
public MongoAdmin(Mongo mongo) {
Assert.notNull(mongo);
@@ -82,7 +84,16 @@ public class MongoAdmin implements MongoAdminOperations {
this.password = password;
}
/**
* Sets the authenticationDatabaseName to use to authenticate with the Mongo database.
*
* @param authenticationDatabaseName The authenticationDatabaseName to use.
*/
public void setAuthenticationDatabaseName(String authenticationDatabaseName) {
this.authenticationDatabaseName = authenticationDatabaseName;
}
DB getDB(String databaseName) {
return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password));
return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName);
}
}

View File

@@ -33,6 +33,7 @@ import com.mongodb.Mongo;
* @author Graeme Rocher
* @author Oliver Gierke
* @author Randy Watler
* @author Thomas Darimont
* @since 1.0
*/
public abstract class MongoDbUtils {
@@ -54,7 +55,7 @@ public abstract class MongoDbUtils {
* @return the {@link DB} connection
*/
public static DB getDB(Mongo mongo, String databaseName) {
return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true);
return doGetDB(mongo, databaseName, UserCredentials.NO_CREDENTIALS, true, databaseName);
}
/**
@@ -66,15 +67,22 @@ public abstract class MongoDbUtils {
* @return the {@link DB} connection
*/
public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials) {
return getDB(mongo, databaseName, credentials, databaseName);
}
public static DB getDB(Mongo mongo, String databaseName, UserCredentials credentials,
String authenticationDatabaseName) {
Assert.notNull(mongo, "No Mongo instance specified!");
Assert.hasText(databaseName, "Database name must be given!");
Assert.notNull(credentials, "Credentials must not be null, use UserCredentials.NO_CREDENTIALS!");
Assert.hasText(authenticationDatabaseName, "Authentication database name must not be null or empty!");
return doGetDB(mongo, databaseName, credentials, true);
return doGetDB(mongo, databaseName, credentials, true, authenticationDatabaseName);
}
private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate) {
private static DB doGetDB(Mongo mongo, String databaseName, UserCredentials credentials, boolean allowCreate,
String authenticationDatabaseName) {
DbHolder dbHolder = (DbHolder) TransactionSynchronizationManager.getResource(mongo);
@@ -103,14 +111,16 @@ public abstract class MongoDbUtils {
DB db = mongo.getDB(databaseName);
boolean credentialsGiven = credentials.hasUsername() && credentials.hasPassword();
synchronized (db) {
DB authDb = databaseName.equals(authenticationDatabaseName) ? db : mongo.getDB(authenticationDatabaseName);
if (credentialsGiven && !db.isAuthenticated()) {
synchronized (authDb) {
if (credentialsGiven && !authDb.isAuthenticated()) {
String username = credentials.getUsername();
String password = credentials.hasPassword() ? credentials.getPassword() : null;
if (!db.authenticate(username, password == null ? null : password.toCharArray())) {
if (!authDb.authenticate(username, password == null ? null : password.toCharArray())) {
throw new CannotGetMongoDbConnectionException("Failed to authenticate to database [" + databaseName + "], "
+ credentials.toString(), databaseName, credentials);
}

View File

@@ -23,6 +23,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.DB;
import com.mongodb.Mongo;
@@ -44,6 +45,8 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
private final boolean mongoInstanceCreated;
private final UserCredentials credentials;
private final PersistenceExceptionTranslator exceptionTranslator;
private final String authenticationDatabaseName;
private WriteConcern writeConcern;
/**
@@ -53,7 +56,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
* @param databaseName database name, not be {@literal null} or empty.
*/
public SimpleMongoDbFactory(Mongo mongo, String databaseName) {
this(mongo, databaseName, UserCredentials.NO_CREDENTIALS, false);
this(mongo, databaseName, null);
}
/**
@@ -64,7 +67,20 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
* @param credentials username and password.
*/
public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials) {
this(mongo, databaseName, credentials, false);
this(mongo, databaseName, credentials, false, null);
}
/**
* Create an instance of SimpleMongoDbFactory given the Mongo instance, database name, and username/password
*
* @param mongo Mongo instance, must not be {@literal null}.
* @param databaseName Database name, must not be {@literal null} or empty.
* @param credentials username and password.
* @param authenticationDatabaseName the database name to use for authentication
*/
public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
String authenticationDatabaseName) {
this(mongo, databaseName, credentials, false, authenticationDatabaseName);
}
/**
@@ -77,11 +93,12 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
*/
@SuppressWarnings("deprecation")
public SimpleMongoDbFactory(MongoURI uri) throws MongoException, UnknownHostException {
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())), true);
this(new Mongo(uri), uri.getDatabase(), new UserCredentials(uri.getUsername(), parseChars(uri.getPassword())),
true, uri.getDatabase());
}
private SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials credentials,
boolean mongoInstanceCreated) {
boolean mongoInstanceCreated, String authenticationDatabaseName) {
Assert.notNull(mongo, "Mongo must not be null");
Assert.hasText(databaseName, "Database name must not be empty");
@@ -93,6 +110,11 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
this.mongoInstanceCreated = mongoInstanceCreated;
this.credentials = credentials == null ? UserCredentials.NO_CREDENTIALS : credentials;
this.exceptionTranslator = new MongoExceptionTranslator();
this.authenticationDatabaseName = StringUtils.hasText(authenticationDatabaseName) ? authenticationDatabaseName
: databaseName;
Assert.isTrue(this.authenticationDatabaseName.matches("[\\w-]+"),
"Authentication database name must only contain letters, numbers, underscores and dashes!");
}
/**
@@ -120,7 +142,7 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory {
Assert.hasText(dbName, "Database name must not be empty.");
DB db = MongoDbUtils.getDB(mongo, dbName, credentials);
DB db = MongoDbUtils.getDB(mongo, dbName, credentials, authenticationDatabaseName);
if (writeConcern != null) {
db.setWriteConcern(writeConcern);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -56,6 +56,13 @@ The name of the database to connect to. Default is 'db'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="authentication-dbname" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the authentication database to connect to. Default is 'db'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@@ -136,6 +136,14 @@ public class AbstractMongoConfigurationUnitTests {
context.close();
}
/**
* @see DATAMONGO-789
*/
@Test
public void authenticationDatabaseShouldDefaultToNull() {
assertThat(new SampleMongoConfiguration().getAuthenticationDatabaseName(), is(nullValue()));
}
private static void assertScanningDisabled(final String value) throws ClassNotFoundException {
AbstractMongoConfiguration configuration = new SampleMongoConfiguration() {

View File

@@ -114,6 +114,24 @@ public class MongoNamespaceTests {
assertEquals("database", getField(dbf, "databaseName"));
}
/**
* @see DATAMONGO-789
*/
@Test
public void testThirdMongoDbFactory() {
assertTrue(ctx.containsBean("thirdMongoDbFactory"));
MongoDbFactory dbf = (MongoDbFactory) ctx.getBean("thirdMongoDbFactory");
Mongo mongo = (Mongo) getField(dbf, "mongo");
assertEquals("localhost", mongo.getAddress().getHost());
assertEquals(27017, mongo.getAddress().getPort());
assertEquals(new UserCredentials("joe", "secret"), getField(dbf, "credentials"));
assertEquals("database", getField(dbf, "databaseName"));
assertEquals("admin", getField(dbf, "authenticationDatabaseName"));
}
/**
* @see DATAMONGO-140
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,9 +39,11 @@ import com.mongodb.MongoException;
* Integration tests for {@link MongoDbUtils}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class MongoDbUtilsIntegrationTests {
static final String AUTHENTICATION_DATABASE_NAME = "admin";
static final String DATABASE_NAME = "dbAuthTests";
static final UserCredentials CREDENTIALS = new UserCredentials("admin", "admin");
@@ -58,14 +60,6 @@ public class MongoDbUtilsIntegrationTests {
mongo = new MongoClient();
template = new MongoTemplate(mongo, DATABASE_NAME);
// Create sample user
template.execute(new DbCallback<Void>() {
public Void doInDB(DB db) throws MongoException, DataAccessException {
db.addUser("admin", "admin".toCharArray());
return null;
}
});
factory = new ThreadPoolExecutorFactoryBean();
factory.setCorePoolSize(2);
factory.setMaxPoolSize(10);
@@ -96,6 +90,14 @@ public class MongoDbUtilsIntegrationTests {
@Test
public void authenticatesCorrectlyInMultithreadedEnvironment() throws Exception {
// Create sample user
template.execute(new DbCallback<Void>() {
public Void doInDB(DB db) throws MongoException, DataAccessException {
db.addUser("admin", "admin".toCharArray());
return null;
}
});
Callable<Void> callable = new Callable<Void>() {
public Void call() throws Exception {
@@ -122,4 +124,45 @@ public class MongoDbUtilsIntegrationTests {
fail("Exception occurred!" + exception);
}
}
/**
* @see DATAMONGO-789
*/
@Test
public void authenticatesCorrectlyWithAuthenticationDB() throws Exception {
// Create sample user
template.execute(new DbCallback<Void>() {
public Void doInDB(DB db) throws MongoException, DataAccessException {
db.getSisterDB("admin").addUser("admin", "admin".toCharArray());
return null;
}
});
Callable<Void> callable = new Callable<Void>() {
public Void call() throws Exception {
try {
DB db = MongoDbUtils.getDB(mongo, DATABASE_NAME, CREDENTIALS, AUTHENTICATION_DATABASE_NAME);
assertThat(db, is(notNullValue()));
} catch (Exception o_O) {
MongoDbUtilsIntegrationTests.this.exception = o_O;
}
return null;
}
};
List<Callable<Void>> callables = new ArrayList<Callable<Void>>();
for (int i = 0; i < 10; i++) {
callables.add(callable);
}
service.invokeAll(callables);
if (exception != null) {
fail("Exception occurred!" + exception);
}
}
}

View File

@@ -77,6 +77,16 @@ public class SimpleMongoDbFactoryUnitTests {
assertThat(ReflectionTestUtils.getField(mongoDbFactory, "databaseName").toString(), is("myDatabase"));
}
/**
* @see DATAMONGO-789
*/
@Test
public void defaultsAuthenticationDatabaseToDatabase() {
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(mongo, "foo");
assertThat(ReflectionTestUtils.getField(factory, "authenticationDatabaseName"), is((Object) "foo"));
}
private void rejectsDatabaseName(String databaseName) {
try {

View File

@@ -37,6 +37,14 @@
dbname="database"
username="joe"
password="secret"/>
<mongo:db-factory id="thirdMongoDbFactory"
host="localhost"
port="27017"
dbname="database"
username="joe"
password="secret"
authentication-dbname="admin"/>
<mongo:mongo id="defaultMongo" host="localhost" port="27017"/>