Skip to content

Commit acab48d

Browse files
DATAMONGO-1158 - Add config option for credentials to mongo-client.
Added credentials attribute to <monog-client> which allows to define a set of credentials used for setting up the MongoClient correctly using authentication data.
1 parent 7f83ac4 commit acab48d

File tree

10 files changed

+367
-13
lines changed

10 files changed

+367
-13
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoClientParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
5050

5151
ParsingUtils.setPropertyValue(builder, element, "port", "port");
5252
ParsingUtils.setPropertyValue(builder, element, "host", "host");
53+
ParsingUtils.setPropertyValue(builder, element, "credentials", "credentials");
5354

5455
MongoParsingUtils.parseMongoClientOptions(element, builder);
5556
MongoParsingUtils.parseReplicaSet(element, builder);
@@ -73,6 +74,10 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
7374
.getReadPreferencePropertyEditorBuilder());
7475
parserContext.registerBeanComponent(readPreferenceEditor);
7576

77+
BeanComponentDefinition credentialsEditor = helper.getComponent(MongoParsingUtils
78+
.getMongoCredentialPropertyEditor());
79+
parserContext.registerBeanComponent(credentialsEditor);
80+
7681
parserContext.popAndRegisterContainingComponent();
7782

7883
return mongoComponent.getBeanDefinition();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.config;
17+
18+
import java.beans.PropertyEditorSupport;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Properties;
22+
23+
import org.springframework.util.StringUtils;
24+
25+
import com.mongodb.MongoCredential;
26+
27+
/**
28+
* Parse a {@link String} to a Collection of {@link MongoCredential}.
29+
*
30+
* @author Christoph Strobl
31+
* @since 1.7
32+
*/
33+
public class MongoCredentialPropertyEditor extends PropertyEditorSupport {
34+
35+
private static final String AUTH_MECHANISM_KEY = "uri.authMechanism";
36+
private static final String USERNAME_PASSWORD_DELIMINATOR = ":";
37+
private static final String DATABASE_DELIMINATOR = "@";
38+
private static final String OPTIONS_DELIMINATOR = "?";
39+
private static final String OPTION_VALUE_DELIMINATOR = "&";
40+
41+
@Override
42+
public void setAsText(String text) throws IllegalArgumentException {
43+
44+
if (!StringUtils.hasText(text)) {
45+
return;
46+
}
47+
48+
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
49+
for (String credentialString : text.split(",")) {
50+
51+
if (!text.contains(USERNAME_PASSWORD_DELIMINATOR) || !text.contains(DATABASE_DELIMINATOR)) {
52+
throw new IllegalArgumentException("Credentials need to be in format 'username:password@database'!");
53+
}
54+
55+
String[] userNameAndPassword = extractUserNameAndPassword(credentialString);
56+
String database = extractDB(credentialString);
57+
Properties options = extractOptions(credentialString);
58+
59+
if (!options.isEmpty()) {
60+
if (options.containsKey(AUTH_MECHANISM_KEY)) {
61+
String authMechanism = options.getProperty(AUTH_MECHANISM_KEY);
62+
if (MongoCredential.GSSAPI_MECHANISM.equals(authMechanism)) {
63+
credentials.add(MongoCredential.createGSSAPICredential(userNameAndPassword[0]));
64+
} else if (MongoCredential.MONGODB_CR_MECHANISM.equals(authMechanism)) {
65+
credentials.add(MongoCredential.createMongoCRCredential(userNameAndPassword[0], database,
66+
userNameAndPassword[1].toCharArray()));
67+
} else if (MongoCredential.MONGODB_X509_MECHANISM.equals(authMechanism)) {
68+
credentials.add(MongoCredential.createMongoX509Credential(userNameAndPassword[0]));
69+
} else if (MongoCredential.PLAIN_MECHANISM.equals(authMechanism)) {
70+
credentials.add(MongoCredential.createPlainCredential(userNameAndPassword[0], database,
71+
userNameAndPassword[1].toCharArray()));
72+
} else if (MongoCredential.SCRAM_SHA_1_MECHANISM.equals(authMechanism)) {
73+
credentials.add(MongoCredential.createScramSha1Credential(userNameAndPassword[0], database,
74+
userNameAndPassword[1].toCharArray()));
75+
} else {
76+
throw new IllegalArgumentException(String.format(
77+
"Cannot create MongoCredentials for unknown auth mechanism '%s'!", authMechanism));
78+
}
79+
}
80+
} else {
81+
credentials.add(MongoCredential.createCredential(userNameAndPassword[0], database,
82+
userNameAndPassword[1].toCharArray()));
83+
}
84+
}
85+
86+
setValue(credentials);
87+
}
88+
89+
private String[] extractUserNameAndPassword(String text) {
90+
91+
int dbSeperationIndex = text.lastIndexOf(DATABASE_DELIMINATOR);
92+
String userNameAndPassword = text.substring(0, dbSeperationIndex);
93+
return userNameAndPassword.split(USERNAME_PASSWORD_DELIMINATOR);
94+
}
95+
96+
private String extractDB(String text) {
97+
98+
int dbSeperationIndex = text.lastIndexOf(DATABASE_DELIMINATOR);
99+
100+
String tmp = text.substring(dbSeperationIndex + 1);
101+
int optionsSeperationIndex = tmp.lastIndexOf(OPTIONS_DELIMINATOR);
102+
103+
return optionsSeperationIndex > -1 ? tmp.substring(0, optionsSeperationIndex) : tmp;
104+
}
105+
106+
private Properties extractOptions(String text) {
107+
108+
int optionsSeperationIndex = text.lastIndexOf(OPTIONS_DELIMINATOR);
109+
int dbSeperationIndex = text.lastIndexOf(OPTIONS_DELIMINATOR);
110+
111+
if (optionsSeperationIndex == -1 || dbSeperationIndex > optionsSeperationIndex) {
112+
return new Properties();
113+
}
114+
115+
Properties properties = new Properties();
116+
117+
for (String option : text.substring(optionsSeperationIndex + 1).split(OPTION_VALUE_DELIMINATOR)) {
118+
String[] optionArgs = option.split("=");
119+
properties.put(optionArgs[0], optionArgs[1]);
120+
}
121+
122+
return properties;
123+
}
124+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoParsingUtils.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,22 @@ static BeanDefinitionBuilder getWriteConcernPropertyEditorBuilder() {
147147
return builder;
148148
}
149149

150+
/**
151+
* One should only register one bean definition but want to have the convenience of using
152+
* AbstractSingleBeanDefinitionParser but have the side effect of registering a 'default' property editor with the
153+
* container.
154+
*/
155+
static BeanDefinitionBuilder getServerAddressPropertyEditorBuilder() {
156+
157+
Map<String, String> customEditors = new ManagedMap<String, String>();
158+
customEditors.put("com.mongodb.ServerAddress[]",
159+
"org.springframework.data.mongodb.config.ServerAddressPropertyEditor");
160+
161+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CustomEditorConfigurer.class);
162+
builder.addPropertyValue("customEditors", customEditors);
163+
return builder;
164+
}
165+
150166
/**
151167
* Returns the {@link BeanDefinitionBuilder} to build a {@link BeanDefinition} for a
152168
* {@link ReadPreferencePropertyEditor}.
@@ -166,18 +182,20 @@ static BeanDefinitionBuilder getReadPreferencePropertyEditorBuilder() {
166182
}
167183

168184
/**
169-
* One should only register one bean definition but want to have the convenience of using
170-
* AbstractSingleBeanDefinitionParser but have the side effect of registering a 'default' property editor with the
171-
* container.
185+
* Returns the {@link BeanDefinitionBuilder} to build a {@link BeanDefinition} for a
186+
* {@link MongoCredentialPropertyEditor}.
187+
*
188+
* @return
189+
* @since 1.7
172190
*/
173-
static BeanDefinitionBuilder getServerAddressPropertyEditorBuilder() {
191+
static BeanDefinitionBuilder getMongoCredentialPropertyEditor() {
174192

175-
Map<String, String> customEditors = new ManagedMap<String, String>();
176-
customEditors.put("com.mongodb.ServerAddress[]",
177-
"org.springframework.data.mongodb.config.ServerAddressPropertyEditor");
193+
Map<String, Class<?>> customEditors = new ManagedMap<String, Class<?>>();
194+
customEditors.put("com.mongodb.MongoCredential[]", MongoCredentialPropertyEditor.class);
178195

179196
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CustomEditorConfigurer.class);
180197
builder.addPropertyValue("customEditors", customEditors);
198+
181199
return builder;
182200
}
183201
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public void setMongoClientOptions(MongoClientOptions mongoClientOptions) {
6868
*
6969
* @param credentials can be {@literal null}.
7070
*/
71-
public void setCredentials(List<MongoCredential> credentials) {
72-
this.credentials = credentials;
71+
public void setCredentials(MongoCredential[] credentials) {
72+
this.credentials = filterNonNullElementsAsList(credentials);
7373
}
7474

7575
public void setReplicaSetSeeds(ServerAddress[] replicaSetSeeds) {

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoFactoryBean.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.springframework.util.StringUtils;
3131

3232
import com.mongodb.Mongo;
33-
import com.mongodb.MongoCredential;
3433
import com.mongodb.MongoOptions;
3534
import com.mongodb.ServerAddress;
3635
import com.mongodb.WriteConcern;
@@ -59,7 +58,6 @@ public class MongoFactoryBean implements FactoryBean<Mongo>, InitializingBean, D
5958
private WriteConcern writeConcern;
6059
private List<ServerAddress> replicaSetSeeds;
6160
private List<ServerAddress> replicaPair;
62-
private List<MongoCredential> credentials;
6361

6462
private PersistenceExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
6563

spring-data-mongodb/src/main/resources/org/springframework/data/mongodb/config/spring-mongo-1.7.xsd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ The comma delimited list of host:port entries to use for replica set/pairs.
586586
<xsd:attribute name="credentials" type="xsd:string" use="optional">
587587
<xsd:annotation>
588588
<xsd:documentation><![CDATA[
589-
The comma delimited list of username:password entries to use for authentication.
589+
The comma delimited list of username:password@database entries to use for authentication. Appending ?uri.authMechanism allows to specify the authentication challenge mechanism.
590590
]]></xsd:documentation>
591591
</xsd:annotation>
592592
</xsd:attribute>

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.mongodb.config;
1717

18+
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
1819
import static org.hamcrest.core.Is.*;
1920
import static org.hamcrest.core.IsInstanceOf.*;
2021
import static org.junit.Assert.*;
@@ -29,6 +30,7 @@
2930
import org.springframework.core.io.ClassPathResource;
3031

3132
import com.mongodb.MongoClient;
33+
import com.mongodb.MongoCredential;
3234
import com.mongodb.ReadPreference;
3335
import com.mongodb.WriteConcern;
3436

@@ -102,4 +104,25 @@ public void createsMongoClientWithDefaultsCorrectly() {
102104
context.close();
103105
}
104106
}
107+
108+
/**
109+
* @see DATAMONGO-1158
110+
*/
111+
@Test
112+
public void createsMongoClientWithCredentialsCorrectly() {
113+
114+
reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml"));
115+
116+
AbstractApplicationContext context = new GenericApplicationContext(factory);
117+
context.refresh();
118+
119+
try {
120+
MongoClient client = context.getBean("mongo-client-with-credentials", MongoClient.class);
121+
122+
assertThat(client.getCredentialsList(),
123+
contains(MongoCredential.createPlainCredential("jon", "snow", "warg".toCharArray())));
124+
} finally {
125+
context.close();
126+
}
127+
}
105128
}

0 commit comments

Comments
 (0)