[JAVA-2117] Document how to use connection string with multiple credentials Created: 17/Feb/16  Updated: 31/Jan/18  Resolved: 31/Jan/18

Status: Closed
Project: Java Driver
Component/s: Documentation
Affects Version/s: 3.0.0
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: James Blackburn Assignee: Unassigned
Resolution: Won't Fix Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
related to JAVA-2656 Deprecate MongoClient constructors th... Closed

 Description   

The MongoClient constructor in Java currently has:

MongoClient(MongoClientURI uri)
MongoClient(List<ServerAddress> seeds, List<MongoCredential> credentialsList, MongoClientOptions options

The former allows connecting using a URI, which is standard and supported by all drivers, but doesn't support custom credentials per database.

Previously we had API like:

mongoClient.getDB(dbname).authenticate(...)

... authenticate seems to have been removed from the 3.x Java driver. But is still avail in other drivers (e.g. pymongo).

We've read:
http://mongodb.github.io/mongo-java-driver/3.2/driver/reference/connecting/authenticating/
https://docs.mongodb.org/manual/reference/connection-string/

But this seems to deal with single credentials for a Mongo instance.

What's the recommended way of using multiple database credentials with the Java driver?



 Comments   
Comment by Jeffrey Yemin [ 31/Jan/18 ]

As multiple credentials for a single MongoClient has been deprecated as of the 3.6 release, closing this as Won't Fix as it's no longer a recommended configuration, and will be unsupported in a future release.

Comment by Jeffrey Yemin [ 31/Jan/18 ]

Linked to JAVA-2656, in which multiple mongo credentials for a MongoClient has been deprecated.

Comment by James Blackburn [ 19/Feb/16 ]

We have approximately 400 databases in our main cluster. In Mongo 2.4 with db level permissioning, we had credentials per database. The app only tried to authenticate to the database when it needed to do so (rather than eagerly authenticating to all databases).

As we're upgrading to MongoDB 3.x we're moving to the new driver.

Note that in the Python world, we have code that looks like this:
https://github.com/manahl/arctic/blob/master/arctic/arctic.py#L367
i.e. it only needs to attempt to authenticate to the DB when it's being used.

Comment by Jeffrey Yemin [ 19/Feb/16 ]

As a consequence of privilege delegation in MongoDB, a MongoClient must eagerly authenticate all credentials that it has been provided. It does not know whether a user has been granted a privilege in another database (e.g. a user defined in database "db1" may have been granted a privilege to read from database "db2").

We could certainly improve the MongoSecurityException message to include information about the credential which failed authentication.

Can you tell us how many credentials/databases you actually have? If it's a small number, one option is to create a separate MongoClient for each.

Please see the driver authentication specification for more information.

Comment by James Blackburn [ 19/Feb/16 ]

It looks like the code eagerly eagerly performs authentication against all databases passed in, rather than doing this lazily as DBs are used.

Previously we were able to selectively authenticate to different databases based on access, which might change dynamically during run.

Also if any of the credentials are wrong the the client blows up, but I have no idea which of the N credentials is wrong.

I have some code trying to access a collection:

CountDownLatch.await(long, TimeUnit) line: 282	
MultiServerCluster(BaseCluster).selectServer(ServerSelector) line: 111	
ClusterBinding$ClusterBindingConnectionSource.<init>(ClusterBinding, ServerSelector) line: 75	
ClusterBinding$ClusterBindingConnectionSource.<init>(ClusterBinding, ServerSelector, ClusterBinding$1) line: 71	
ClusterBinding.getReadConnectionSource() line: 63	
OperationHelper.withConnection(ReadBinding, CallableWithConnectionAndSource<T>) line: 210	
FindOperation<T>.execute(ReadBinding) line: 480	
FindOperation<T>.execute(ReadBinding) line: 77	
MongoClient(Mongo).execute(ReadOperation<T>, ReadPreference) line: 773	
Mongo$2.execute(ReadOperation<T>, ReadPreference) line: 760	
DBCollection.findOne(DBObject, DBObject, DBObject, ReadPreference, ReadConcern, long, TimeUnit) line: 777	
DBCollection.findOne(DBObject, DBObject, DBObject, ReadPreference) line: 747	
DBCollection.findOne(DBObject) line: 694	
Mongoose.getLibrary(String) line: 132	

Meanwhile the cluster monitor code blows up because a single cred isn't correct (for a DB I'm not using) - and I don't have any idea which DB is at fault.

16/02/19 11:23:54 INFO driver.cluster: Cluster created with settings {hosts=[cn15-ib:27119, cn16-ib:27119, localhost:27119], mode=MULTIPLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
16/02/19 11:23:54 INFO driver.cluster: Adding discovered server cn15-ib:27119 to client view of cluster
16/02/19 11:23:54 INFO driver.cluster: Adding discovered server cn16-ib:27119 to client view of cluster
16/02/19 11:23:54 INFO driver.cluster: Adding discovered server localhost:27119 to client view of cluster
16/02/19 11:23:54 INFO driver.cluster: No server chosen by ReadPreferenceServerSelector{readPreference=nearest} from cluster description ClusterDescription{type=UNKNOWN, connectionMode=MULTIPLE, all=[ServerDescription{address=cn15-ib:27119, type=UNKNOWN, state=CONNECTING}, ServerDescription{address=cn16-ib:27119, type=UNKNOWN, state=CONNECTING}, ServerDescription{address=localhost:27119, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
16/02/19 11:23:54 INFO driver.cluster: Exception in monitor thread while connecting to server cn16-ib:27119
com.mongodb.MongoSecurityException: Exception authenticating
	at com.mongodb.connection.NativeAuthenticator.authenticate(NativeAuthenticator.java:48)
	at com.mongodb.connection.DefaultAuthenticator.authenticate(DefaultAuthenticator.java:32)
	at com.mongodb.connection.InternalStreamConnectionInitializer.authenticateAll(InternalStreamConnectionInitializer.java:99)
	at com.mongodb.connection.InternalStreamConnectionInitializer.initialize(InternalStreamConnectionInitializer.java:44)
	at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:115)
	at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:128)
	at java.lang.Thread.run(Thread.java:745)
Caused by: com.mongodb.MongoCommandException: Command failed with error 18: 'auth fails' on server cn16-ib:27119. The full response is { "code" : 18, "ok" : 0.0, "errmsg" : "auth fails" }
	at com.mongodb.connection.CommandHelper.createCommandFailureException(CommandHelper.java:170)
	at com.mongodb.connection.CommandHelper.receiveCommandResult(CommandHelper.java:123)
	at com.mongodb.connection.CommandHelper.executeCommand(CommandHelper.java:32)
	at com.mongodb.connection.NativeAuthenticator.authenticate(NativeAuthenticator.java:46)
	... 6 more
16/02/19 11:23:54 INFO driver.cluster: Exception in monitor thread while connecting to server cn15-ib:27119
com.mongodb.MongoSecurityException: Exception authenticating
	at com.mongodb.connection.NativeAuthenticator.authenticate(NativeAuthenticator.java:48)
	at com.mongodb.connection.DefaultAuthenticator.authenticate(DefaultAuthenticator.java:32)
	at com.mongodb.connection.InternalStreamConnectionInitializer.authenticateAll(InternalStreamConnectionInitializer.java:99)
	at com.mongodb.connection.InternalStreamConnectionInitializer.initialize(InternalStreamConnectionInitializer.java:44)
	at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:115)
	at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:128)
	at java.lang.Thread.run(Thread.java:745)
Caused by: com.mongodb.MongoCommandException: Command failed with error 18: 'auth fails' on server cn15-ib:27119. The full response is { "code" : 18, "ok" : 0.0, "errmsg" : "auth fails" }
	at com.mongodb.connection.CommandHelper.createCommandFailureException(CommandHelper.java:170)
	at com.mongodb.connection.CommandHelper.receiveCommandResult(CommandHelper.java:123)
	at com.mongodb.connection.CommandHelper.executeCommand(CommandHelper.java:32)
	at com.mongodb.connection.NativeAuthenticator.authenticate(NativeAuthenticator.java:46)
	... 6 more
16/02/19 11:23:54 INFO driver.cluster: Exception in monitor thread while connecting to server localhost:27119
com.mongodb.MongoSecurityException: Exception authenticating
	at com.mongodb.connection.NativeAuthenticator.authenticate(NativeAuthenticator.java:48)
	at com.mongodb.connection.DefaultAuthenticator.authenticate(DefaultAuthenticator.java:32)
	at com.mongodb.connection.InternalStreamConnectionInitializer.authenticateAll(InternalStreamConnectionInitializer.java:99)
	at com.mongodb.connection.InternalStreamConnectionInitializer.initialize(InternalStreamConnectionInitializer.java:44)
	at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:115)
	at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:128)
	at java.lang.Thread.run(Thread.java:745)
Caused by: com.mongodb.MongoCommandException: Command failed with error 18: 'auth fails' on server localhost:27119. The full response is { "code" : 18, "ok" : 0.0, "errmsg" : "auth fails" }
	at com.mongodb.connection.CommandHelper.createCommandFailureException(CommandHelper.java:170)
	at com.mongodb.connection.CommandHelper.receiveCommandResult(CommandHelper.java:123)
	at com.mongodb.connection.CommandHelper.executeCommand(CommandHelper.java:32)
	at com.mongodb.connection.NativeAuthenticator.authenticate(NativeAuthenticator.java:46)
	... 6 more
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 29.998 sec <<< FAILURE!

Comment by Jeffrey Yemin [ 17/Feb/16 ]

I agree. I changed the issue type to Improvement and added the Documentation component. We'll try to get this in the next release of the docs.

Comment by James Blackburn [ 17/Feb/16 ]

Many thanks, will try that. Would be great if that were in the docs.

Comment by Jeffrey Yemin [ 17/Feb/16 ]

If you still want to use a connection string for everything but credentials, here's one way to do it:

    public static void main(String[] args) {
        // Create an options builder for any options that are not settable via URI
        MongoClientOptions.Builder builder = MongoClientOptions.builder()
                .cursorFinalizerEnabled(false);
 
        // Create a URI with the builder and the connection string, with no auth info.  Options set via connection string
        // will override options in the builder
        MongoClientURI uri = new MongoClientURI("mongodb://host1,host2,host3/?maxPoolSize=200", builder);
 
        // Create the list of credentials
        List<MongoCredential> credentials = Arrays.asList(MongoCredential.createCredential("user1", "db1", "pass1".toCharArray()),
                MongoCredential.createCredential("user2", "db2", "pass2".toCharArray()));
 
        // Create the client with the hosts list, the credentials list, and the merged options
        MongoClient client = new MongoClient(getHosts(uri.getHosts()), credentials, uri.getOptions());
    }
 
    // Convert List<String> to List<ServerAddress> .... :(
    private static List<ServerAddress> getHosts(final List<String> hostsList) {
        List<ServerAddress> serverAddressList = new ArrayList<>(hostsList.size());
        for (String host : hostsList) {
            serverAddressList.add(new ServerAddress(host));
        }
        return serverAddressList;
    }

Generated at Thu Feb 08 08:56:22 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.