Skip to content

Commit 8919d40

Browse files
author
fuchaohong
committed
HADOOP-19598. LdapAuthenticationHandler supports configuring multiple ldapUrls.
1 parent 43b5183 commit 8919d40

File tree

2 files changed

+74
-28
lines changed

2 files changed

+74
-28
lines changed

hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/LdapAuthenticationHandler.java

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
import java.io.IOException;
1717
import java.nio.charset.StandardCharsets;
18+
import java.util.Arrays;
1819
import java.util.Hashtable;
20+
import java.util.Iterator;
1921
import java.util.Properties;
2022

2123
import javax.naming.Context;
@@ -100,11 +102,19 @@ public class LdapAuthenticationHandler implements AuthenticationHandler {
100102
*/
101103
public static final String ENABLE_START_TLS = TYPE + ".enablestarttls";
102104

105+
/**
106+
* Constant for the number of attempts per LDAP server URL.
107+
*/
108+
public static final String NUM_ATTEMPTS = TYPE + ".numattempts";
109+
110+
public static final String NUM_ATTEMPTS_DEFAULT = "2";
111+
103112
private String ldapDomain;
104113
private String baseDN;
105-
private String providerUrl;
106114
private Boolean enableStartTls;
107115
private Boolean disableHostNameVerification;
116+
private Iterator<String> ldapUrls;
117+
private int numAttempts;
108118

109119
/**
110120
* Configure StartTLS LDAP extension for this handler.
@@ -138,25 +148,31 @@ public String getType() {
138148
@Override
139149
public void init(Properties config) throws ServletException {
140150
this.baseDN = config.getProperty(BASE_DN);
141-
this.providerUrl = config.getProperty(PROVIDER_URL);
151+
String providerUrl = config.getProperty(PROVIDER_URL);
152+
if (providerUrl == null) {
153+
throw new NullPointerException("The LDAP URI can not be null");
154+
}
155+
this.ldapUrls = Arrays.asList(providerUrl.split(",")).iterator();
142156
this.ldapDomain = config.getProperty(LDAP_BIND_DOMAIN);
143157
this.enableStartTls =
144158
Boolean.valueOf(config.getProperty(ENABLE_START_TLS, "false"));
159+
this.numAttempts =
160+
Integer.parseInt(config.getProperty(NUM_ATTEMPTS, NUM_ATTEMPTS_DEFAULT));
145161

146-
if (this.providerUrl == null) {
147-
throw new NullPointerException("The LDAP URI can not be null");
148-
}
149162
if (!((this.baseDN == null)
150163
^ (this.ldapDomain == null))) {
151164
throw new IllegalArgumentException(
152165
"Either LDAP base DN or LDAP domain value needs to be specified");
153166
}
154167
if (this.enableStartTls) {
155-
String tmp = this.providerUrl.toLowerCase();
156-
if (tmp.startsWith("ldaps")) {
157-
throw new IllegalArgumentException(
158-
"Can not use ldaps and StartTLS option at the same time");
168+
while (ldapUrls.hasNext()) {
169+
String tmp = ldapUrls.next().toLowerCase();
170+
if (tmp.startsWith("ldaps")) {
171+
throw new IllegalArgumentException(
172+
"Can not use ldaps and StartTLS option at the same time");
173+
}
159174
}
175+
160176
}
161177
}
162178

@@ -234,22 +250,33 @@ private AuthenticationToken authenticateUser(String userName,
234250
bindDN = "uid=" + userName + "," + baseDN;
235251
}
236252

237-
if (this.enableStartTls) {
238-
authenticateWithTlsExtension(bindDN, password);
239-
} else {
240-
authenticateWithoutTlsExtension(bindDN, password);
253+
while (ldapUrls.hasNext()){
254+
String currentLdapUrl = ldapUrls.next();
255+
for (int attempt = 1; attempt <= numAttempts; attempt++) {
256+
try {
257+
if (this.enableStartTls) {
258+
authenticateWithTlsExtension(bindDN, password, currentLdapUrl);
259+
} else {
260+
authenticateWithoutTlsExtension(bindDN, password, currentLdapUrl);
261+
}
262+
return new AuthenticationToken(userName, userName, TYPE);
263+
} catch (AuthenticationException e) {
264+
logger.warn("Failed to authenticate for user {} (attempt={}/{}) using {}. " +
265+
"Exception: ", bindDN, attempt, numAttempts, currentLdapUrl, e);
266+
}
267+
}
241268
}
242269

243-
return new AuthenticationToken(userName, userName, TYPE);
270+
return null;
244271
}
245272

246-
private void authenticateWithTlsExtension(String userDN, String password)
247-
throws AuthenticationException {
273+
private void authenticateWithTlsExtension(String userDN, String password,
274+
String ldapUrl) throws AuthenticationException {
248275
LdapContext ctx = null;
249276
Hashtable<String, Object> env = new Hashtable<String, Object>();
250277
env.put(Context.INITIAL_CONTEXT_FACTORY,
251278
"com.sun.jndi.ldap.LdapCtxFactory");
252-
env.put(Context.PROVIDER_URL, providerUrl);
279+
env.put(Context.PROVIDER_URL, ldapUrl);
253280

254281
try {
255282
// Create initial context
@@ -290,12 +317,12 @@ public boolean verify(String hostname, SSLSession session) {
290317
}
291318
}
292319

293-
private void authenticateWithoutTlsExtension(String userDN, String password)
294-
throws AuthenticationException {
320+
private void authenticateWithoutTlsExtension(String userDN, String password,
321+
String ldapUrl) throws AuthenticationException {
295322
Hashtable<String, Object> env = new Hashtable<String, Object>();
296323
env.put(Context.INITIAL_CONTEXT_FACTORY,
297324
"com.sun.jndi.ldap.LdapCtxFactory");
298-
env.put(Context.PROVIDER_URL, providerUrl);
325+
env.put(Context.PROVIDER_URL, ldapUrl);
299326
env.put(Context.SECURITY_AUTHENTICATION, SECURITY_AUTHENTICATION);
300327
env.put(Context.SECURITY_PRINCIPAL, userDN);
301328
env.put(Context.SECURITY_CREDENTIALS, password);

hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestLdapAuthenticationHandler.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,32 @@ public void testRequestWithWrongCredentials() throws Exception {
160160
when(request.getHeader(HttpConstants.AUTHORIZATION_HEADER))
161161
.thenReturn(authHeader);
162162

163-
try {
164-
handler.authenticate(request, response);
165-
fail();
166-
} catch (AuthenticationException ex) {
167-
// Expected
168-
} catch (Exception ex) {
169-
fail();
170-
}
163+
assertNull(handler.authenticate(request, response));
164+
}
165+
166+
@Test
167+
@Timeout(value = 60, unit = TimeUnit.SECONDS)
168+
public void testMultiLdapUrl() throws Exception {
169+
LdapAuthenticationHandler handler1 = new LdapAuthenticationHandler();
170+
Properties p1 = new Properties();
171+
p1.setProperty(BASE_DN, LDAP_BASE_DN);
172+
p1.setProperty(PROVIDER_URL, String.format("ldap://errorLdap:389,ldap://%s:%s",
173+
LDAP_SERVER_ADDR, getLdapServer().getPort()));
174+
handler1.init(p1);
175+
176+
HttpServletRequest request = mock(HttpServletRequest.class);
177+
HttpServletResponse response = mock(HttpServletResponse.class);
178+
179+
final Base64 base64 = new Base64(0);
180+
String credentials = base64.encodeToString("bjones:******".getBytes());
181+
String authHeader = HttpConstants.BASIC + " " + credentials;
182+
when(request.getHeader(HttpConstants.AUTHORIZATION_HEADER))
183+
.thenReturn(authHeader);
184+
AuthenticationToken token = handler1.authenticate(request, response);
185+
assertNotNull(token);
186+
verify(response).setStatus(HttpServletResponse.SC_OK);
187+
assertEquals(TYPE, token.getType());
188+
assertEquals("bjones", token.getUserName());
189+
assertEquals("bjones", token.getName());
171190
}
172191
}

0 commit comments

Comments
 (0)