1090 lines
42 KiB
Diff
1090 lines
42 KiB
Diff
|
|
From 94b22be79a82a7238b022bcaa61b574e6a56691e Mon Sep 17 00:00:00 2001
|
||
|
|
From: remm <remm@apache.org>
|
||
|
|
Date: Thu, 30 Jan 2020 17:22:51 +0100
|
||
|
|
Subject: [PATCH] Add connection pool to JNDI realm
|
||
|
|
|
||
|
|
This implements a TODO from the class javadoc header.
|
||
|
|
As described in the javadoc, the idea is to use a pool to avoid blocking
|
||
|
|
on a single connection, which could possibly become a bottleneck in some
|
||
|
|
cases. The message formats need to be kept along with the connection
|
||
|
|
since they are not thread safe.
|
||
|
|
Preserve the default behavior: sync without pooling (using a Lock object
|
||
|
|
which is more flexible).
|
||
|
|
I may backport this since this is limited to the JNDI realm, but only
|
||
|
|
once it is confirmed to be regression free. Tested with ApacheDS but my
|
||
|
|
LDAP skills are very limited.
|
||
|
|
---
|
||
|
|
java/org/apache/catalina/realm/JNDIRealm.java | 442 ++++++++++--------
|
||
|
|
.../catalina/realm/LocalStrings.properties | 3 +-
|
||
|
|
.../apache/catalina/realm/TestJNDIRealm.java | 7 +-
|
||
|
|
webapps/docs/changelog.xml | 3 +
|
||
|
|
webapps/docs/config/realm.xml | 7 +
|
||
|
|
5 files changed, 277 insertions(+), 185 deletions(-)
|
||
|
|
|
||
|
|
diff --git a/java/org/apache/catalina/realm/JNDIRealm.java b/java/org/apache/catalina/realm/JNDIRealm.java
|
||
|
|
index 505dd13..b624c5b 100644
|
||
|
|
--- a/java/org/apache/catalina/realm/JNDIRealm.java
|
||
|
|
+++ b/java/org/apache/catalina/realm/JNDIRealm.java
|
||
|
|
@@ -33,6 +33,8 @@ import java.util.List;
|
||
|
|
import java.util.Map;
|
||
|
|
import java.util.Map.Entry;
|
||
|
|
import java.util.Set;
|
||
|
|
+import java.util.concurrent.locks.Lock;
|
||
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
||
|
|
|
||
|
|
import javax.naming.AuthenticationException;
|
||
|
|
import javax.naming.CommunicationException;
|
||
|
|
@@ -62,6 +64,7 @@ import javax.net.ssl.SSLSession;
|
||
|
|
import javax.net.ssl.SSLSocketFactory;
|
||
|
|
|
||
|
|
import org.apache.catalina.LifecycleException;
|
||
|
|
+import org.apache.tomcat.util.collections.SynchronizedStack;
|
||
|
|
import org.ietf.jgss.GSSCredential;
|
||
|
|
|
||
|
|
/**
|
||
|
|
@@ -166,10 +169,6 @@ import org.ietf.jgss.GSSCredential;
|
||
|
|
* directory server itself.</li>
|
||
|
|
* </ul>
|
||
|
|
*
|
||
|
|
- * <p><strong>TODO</strong> - Support connection pooling (including message
|
||
|
|
- * format objects) so that <code>authenticate()</code> does not have to be
|
||
|
|
- * synchronized.</p>
|
||
|
|
- *
|
||
|
|
* <p><strong>WARNING</strong> - There is a reported bug against the Netscape
|
||
|
|
* provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
|
||
|
|
* successfully authenticated a non-existing user. The
|
||
|
|
@@ -208,12 +207,6 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
protected String connectionURL = null;
|
||
|
|
|
||
|
|
|
||
|
|
- /**
|
||
|
|
- * The directory context linking us to our directory server.
|
||
|
|
- */
|
||
|
|
- protected DirContext context = null;
|
||
|
|
-
|
||
|
|
-
|
||
|
|
/**
|
||
|
|
* The JNDI context factory used to acquire our InitialContext. By
|
||
|
|
* default, assumes use of an LDAP server using the standard JNDI LDAP
|
||
|
|
@@ -282,13 +275,6 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
private boolean userSearchAsUser = false;
|
||
|
|
|
||
|
|
|
||
|
|
- /**
|
||
|
|
- * The MessageFormat object associated with the current
|
||
|
|
- * <code>userSearch</code>.
|
||
|
|
- */
|
||
|
|
- protected MessageFormat userSearchFormat = null;
|
||
|
|
-
|
||
|
|
-
|
||
|
|
/**
|
||
|
|
* Should we search the entire subtree for matching users?
|
||
|
|
*/
|
||
|
|
@@ -328,32 +314,12 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
protected String userPattern = null;
|
||
|
|
|
||
|
|
|
||
|
|
- /**
|
||
|
|
- * An array of MessageFormat objects associated with the current
|
||
|
|
- * <code>userPatternArray</code>.
|
||
|
|
- */
|
||
|
|
- protected MessageFormat[] userPatternFormatArray = null;
|
||
|
|
-
|
||
|
|
/**
|
||
|
|
* The base element for role searches.
|
||
|
|
*/
|
||
|
|
protected String roleBase = "";
|
||
|
|
|
||
|
|
|
||
|
|
- /**
|
||
|
|
- * The MessageFormat object associated with the current
|
||
|
|
- * <code>roleBase</code>.
|
||
|
|
- */
|
||
|
|
- protected MessageFormat roleBaseFormat = null;
|
||
|
|
-
|
||
|
|
-
|
||
|
|
- /**
|
||
|
|
- * The MessageFormat object associated with the current
|
||
|
|
- * <code>roleSearch</code>.
|
||
|
|
- */
|
||
|
|
- protected MessageFormat roleFormat = null;
|
||
|
|
-
|
||
|
|
-
|
||
|
|
/**
|
||
|
|
* The name of an attribute in the user's entry containing
|
||
|
|
* roles for that user
|
||
|
|
@@ -497,6 +463,30 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
*/
|
||
|
|
private String sslProtocol;
|
||
|
|
|
||
|
|
+ /**
|
||
|
|
+ * Non pooled connection to our directory server.
|
||
|
|
+ */
|
||
|
|
+ protected JNDIConnection singleConnection = new JNDIConnection();
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The lock to ensure single connection thread safety.
|
||
|
|
+ */
|
||
|
|
+ protected final Lock singleConnectionLock = new ReentrantLock();
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Connection pool.
|
||
|
|
+ */
|
||
|
|
+ protected SynchronizedStack<JNDIConnection> connectionPool = null;
|
||
|
|
+
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The pool size limit. If 1, pooling is not used.
|
||
|
|
+ */
|
||
|
|
+ protected int connectionPoolSize = 1;
|
||
|
|
+
|
||
|
|
+
|
||
|
|
// ------------------------------------------------------------- Properties
|
||
|
|
|
||
|
|
/**
|
||
|
|
@@ -717,13 +707,8 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* @param userSearch The new user search pattern
|
||
|
|
*/
|
||
|
|
public void setUserSearch(String userSearch) {
|
||
|
|
-
|
||
|
|
this.userSearch = userSearch;
|
||
|
|
- if (userSearch == null)
|
||
|
|
- userSearchFormat = null;
|
||
|
|
- else
|
||
|
|
- userSearchFormat = new MessageFormat(userSearch);
|
||
|
|
-
|
||
|
|
+ singleConnection = create();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@@ -796,13 +781,8 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* @param roleBase The new base element
|
||
|
|
*/
|
||
|
|
public void setRoleBase(String roleBase) {
|
||
|
|
-
|
||
|
|
this.roleBase = roleBase;
|
||
|
|
- if (roleBase == null)
|
||
|
|
- roleBaseFormat = null;
|
||
|
|
- else
|
||
|
|
- roleBaseFormat = new MessageFormat(roleBase);
|
||
|
|
-
|
||
|
|
+ singleConnection = create();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@@ -844,13 +824,8 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* @param roleSearch The new role search pattern
|
||
|
|
*/
|
||
|
|
public void setRoleSearch(String roleSearch) {
|
||
|
|
-
|
||
|
|
this.roleSearch = roleSearch;
|
||
|
|
- if (roleSearch == null)
|
||
|
|
- roleFormat = null;
|
||
|
|
- else
|
||
|
|
- roleFormat = new MessageFormat(roleSearch);
|
||
|
|
-
|
||
|
|
+ singleConnection = create();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@@ -960,18 +935,12 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* @param userPattern The new user pattern
|
||
|
|
*/
|
||
|
|
public void setUserPattern(String userPattern) {
|
||
|
|
-
|
||
|
|
this.userPattern = userPattern;
|
||
|
|
- if (userPattern == null)
|
||
|
|
+ if (userPattern == null) {
|
||
|
|
userPatternArray = null;
|
||
|
|
- else {
|
||
|
|
+ } else {
|
||
|
|
userPatternArray = parseUserPatternString(userPattern);
|
||
|
|
- int len = this.userPatternArray.length;
|
||
|
|
- userPatternFormatArray = new MessageFormat[len];
|
||
|
|
- for (int i=0; i < len; i++) {
|
||
|
|
- userPatternFormatArray[i] =
|
||
|
|
- new MessageFormat(userPatternArray[i]);
|
||
|
|
- }
|
||
|
|
+ singleConnection = create();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@@ -1151,6 +1120,22 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
this.cipherSuites = suites;
|
||
|
|
}
|
||
|
|
|
||
|
|
+ /**
|
||
|
|
+ * @return the connection pool size, or the default value 1 if pooling
|
||
|
|
+ * is disabled
|
||
|
|
+ */
|
||
|
|
+ public int getConnectionPoolSize() {
|
||
|
|
+ return connectionPoolSize;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Set the connection pool size
|
||
|
|
+ * @param connectionPoolSize the new pool size
|
||
|
|
+ */
|
||
|
|
+ public void setConnectionPoolSize(int connectionPoolSize) {
|
||
|
|
+ this.connectionPoolSize = connectionPoolSize;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
/**
|
||
|
|
* @return name of the {@link HostnameVerifier} class used for connections
|
||
|
|
* using StartTLS, or the empty string, if the default verifier
|
||
|
|
@@ -1269,20 +1254,20 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
@Override
|
||
|
|
public Principal authenticate(String username, String credentials) {
|
||
|
|
|
||
|
|
- DirContext context = null;
|
||
|
|
+ JNDIConnection connection = null;
|
||
|
|
Principal principal = null;
|
||
|
|
|
||
|
|
try {
|
||
|
|
|
||
|
|
// Ensure that we have a directory context available
|
||
|
|
- context = open();
|
||
|
|
+ connection = get();
|
||
|
|
|
||
|
|
// Occasionally the directory context will timeout. Try one more
|
||
|
|
// time before giving up.
|
||
|
|
try {
|
||
|
|
|
||
|
|
// Authenticate the specified username if possible
|
||
|
|
- principal = authenticate(context, username, credentials);
|
||
|
|
+ principal = authenticate(connection, username, credentials);
|
||
|
|
|
||
|
|
} catch (NullPointerException | NamingException e) {
|
||
|
|
/*
|
||
|
|
@@ -1304,19 +1289,18 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
|
||
|
|
|
||
|
|
// close the connection so we know it will be reopened.
|
||
|
|
- if (context != null)
|
||
|
|
- close(context);
|
||
|
|
+ close(connection);
|
||
|
|
|
||
|
|
// open a new directory context.
|
||
|
|
- context = open();
|
||
|
|
+ connection = get();
|
||
|
|
|
||
|
|
// Try the authentication again.
|
||
|
|
- principal = authenticate(context, username, credentials);
|
||
|
|
+ principal = authenticate(connection, username, credentials);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Release this context
|
||
|
|
- release(context);
|
||
|
|
+ release(connection);
|
||
|
|
|
||
|
|
// Return the authenticated Principal (if any)
|
||
|
|
return principal;
|
||
|
|
@@ -1327,8 +1311,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
containerLog.error(sm.getString("jndiRealm.exception"), e);
|
||
|
|
|
||
|
|
// Close the connection so that it gets reopened next time
|
||
|
|
- if (context != null)
|
||
|
|
- close(context);
|
||
|
|
+ close(connection);
|
||
|
|
|
||
|
|
// Return "not authenticated" for this request
|
||
|
|
if (containerLog.isDebugEnabled())
|
||
|
|
@@ -1350,7 +1333,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* Return the Principal associated with the specified username and
|
||
|
|
* credentials, if there is one; otherwise return <code>null</code>.
|
||
|
|
*
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username Username of the Principal to look up
|
||
|
|
* @param credentials Password or other credentials to use in
|
||
|
|
* authenticating this username
|
||
|
|
@@ -1358,7 +1341,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
*
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*/
|
||
|
|
- public synchronized Principal authenticate(DirContext context,
|
||
|
|
+ public Principal authenticate(JNDIConnection connection,
|
||
|
|
String username,
|
||
|
|
String credentials)
|
||
|
|
throws NamingException {
|
||
|
|
@@ -1372,16 +1355,16 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
|
||
|
|
if (userPatternArray != null) {
|
||
|
|
for (int curUserPattern = 0;
|
||
|
|
- curUserPattern < userPatternFormatArray.length;
|
||
|
|
+ curUserPattern < userPatternArray.length;
|
||
|
|
curUserPattern++) {
|
||
|
|
// Retrieve user information
|
||
|
|
- User user = getUser(context, username, credentials, curUserPattern);
|
||
|
|
+ User user = getUser(connection, username, credentials, curUserPattern);
|
||
|
|
if (user != null) {
|
||
|
|
try {
|
||
|
|
// Check the user's credentials
|
||
|
|
- if (checkCredentials(context, user, credentials)) {
|
||
|
|
+ if (checkCredentials(connection.context, user, credentials)) {
|
||
|
|
// Search for additional roles
|
||
|
|
- List<String> roles = getRoles(context, user);
|
||
|
|
+ List<String> roles = getRoles(connection, user);
|
||
|
|
if (containerLog.isDebugEnabled()) {
|
||
|
|
containerLog.debug("Found roles: " + roles.toString());
|
||
|
|
}
|
||
|
|
@@ -1400,16 +1383,16 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
return null;
|
||
|
|
} else {
|
||
|
|
// Retrieve user information
|
||
|
|
- User user = getUser(context, username, credentials);
|
||
|
|
+ User user = getUser(connection, username, credentials);
|
||
|
|
if (user == null)
|
||
|
|
return null;
|
||
|
|
|
||
|
|
// Check the user's credentials
|
||
|
|
- if (!checkCredentials(context, user, credentials))
|
||
|
|
+ if (!checkCredentials(connection.context, user, credentials))
|
||
|
|
return null;
|
||
|
|
|
||
|
|
// Search for additional roles
|
||
|
|
- List<String> roles = getRoles(context, user);
|
||
|
|
+ List<String> roles = getRoles(connection, user);
|
||
|
|
if (containerLog.isDebugEnabled()) {
|
||
|
|
containerLog.debug("Found roles: " + roles.toString());
|
||
|
|
}
|
||
|
|
@@ -1425,17 +1408,17 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* with the specified username, if found in the directory;
|
||
|
|
* otherwise return <code>null</code>.
|
||
|
|
*
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username Username to be looked up
|
||
|
|
* @return the User object
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*
|
||
|
|
- * @see #getUser(DirContext, String, String, int)
|
||
|
|
+ * @see #getUser(JNDIConnection, String, String, int)
|
||
|
|
*/
|
||
|
|
- protected User getUser(DirContext context, String username)
|
||
|
|
+ protected User getUser(JNDIConnection connection, String username)
|
||
|
|
throws NamingException {
|
||
|
|
|
||
|
|
- return getUser(context, username, null, -1);
|
||
|
|
+ return getUser(connection, username, null, -1);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@@ -1444,18 +1427,18 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* with the specified username, if found in the directory;
|
||
|
|
* otherwise return <code>null</code>.
|
||
|
|
*
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username Username to be looked up
|
||
|
|
* @param credentials User credentials (optional)
|
||
|
|
* @return the User object
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*
|
||
|
|
- * @see #getUser(DirContext, String, String, int)
|
||
|
|
+ * @see #getUser(JNDIConnection, String, String, int)
|
||
|
|
*/
|
||
|
|
- protected User getUser(DirContext context, String username, String credentials)
|
||
|
|
+ protected User getUser(JNDIConnection connection, String username, String credentials)
|
||
|
|
throws NamingException {
|
||
|
|
|
||
|
|
- return getUser(context, username, credentials, -1);
|
||
|
|
+ return getUser(connection, username, credentials, -1);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@@ -1470,14 +1453,14 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* configuration attribute is specified, all values of that
|
||
|
|
* attribute are retrieved from the directory entry.
|
||
|
|
*
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username Username to be looked up
|
||
|
|
* @param credentials User credentials (optional)
|
||
|
|
* @param curUserPattern Index into userPatternFormatArray
|
||
|
|
* @return the User object
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*/
|
||
|
|
- protected User getUser(DirContext context, String username,
|
||
|
|
+ protected User getUser(JNDIConnection connection, String username,
|
||
|
|
String credentials, int curUserPattern)
|
||
|
|
throws NamingException {
|
||
|
|
|
||
|
|
@@ -1496,8 +1479,8 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
list.toArray(attrIds);
|
||
|
|
|
||
|
|
// Use pattern or search for user entry
|
||
|
|
- if (userPatternFormatArray != null && curUserPattern >= 0) {
|
||
|
|
- user = getUserByPattern(context, username, credentials, attrIds, curUserPattern);
|
||
|
|
+ if (userPatternArray != null && curUserPattern >= 0) {
|
||
|
|
+ user = getUserByPattern(connection, username, credentials, attrIds, curUserPattern);
|
||
|
|
if (containerLog.isDebugEnabled()) {
|
||
|
|
containerLog.debug("Found user by pattern [" + user + "]");
|
||
|
|
}
|
||
|
|
@@ -1505,12 +1488,12 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
boolean thisUserSearchAsUser = isUserSearchAsUser();
|
||
|
|
try {
|
||
|
|
if (thisUserSearchAsUser) {
|
||
|
|
- userCredentialsAdd(context, username, credentials);
|
||
|
|
+ userCredentialsAdd(connection.context, username, credentials);
|
||
|
|
}
|
||
|
|
- user = getUserBySearch(context, username, attrIds);
|
||
|
|
+ user = getUserBySearch(connection, username, attrIds);
|
||
|
|
} finally {
|
||
|
|
if (thisUserSearchAsUser) {
|
||
|
|
- userCredentialsRemove(context);
|
||
|
|
+ userCredentialsRemove(connection.context);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (containerLog.isDebugEnabled()) {
|
||
|
|
@@ -1588,7 +1571,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* username and return a User object; otherwise return
|
||
|
|
* <code>null</code>.
|
||
|
|
*
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username The username
|
||
|
|
* @param credentials User credentials (optional)
|
||
|
|
* @param attrIds String[]containing names of attributes to
|
||
|
|
@@ -1597,7 +1580,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
* @see #getUserByPattern(DirContext, String, String[], String)
|
||
|
|
*/
|
||
|
|
- protected User getUserByPattern(DirContext context,
|
||
|
|
+ protected User getUserByPattern(JNDIConnection connection,
|
||
|
|
String username,
|
||
|
|
String credentials,
|
||
|
|
String[] attrIds,
|
||
|
|
@@ -1606,25 +1589,25 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
|
||
|
|
User user = null;
|
||
|
|
|
||
|
|
- if (username == null || userPatternFormatArray[curUserPattern] == null)
|
||
|
|
+ if (username == null || userPatternArray[curUserPattern] == null)
|
||
|
|
return null;
|
||
|
|
|
||
|
|
// Form the dn from the user pattern
|
||
|
|
- String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
|
||
|
|
+ String dn = connection.userPatternFormatArray[curUserPattern].format(new String[] { username });
|
||
|
|
|
||
|
|
try {
|
||
|
|
- user = getUserByPattern(context, username, attrIds, dn);
|
||
|
|
+ user = getUserByPattern(connection.context, username, attrIds, dn);
|
||
|
|
} catch (NameNotFoundException e) {
|
||
|
|
return null;
|
||
|
|
} catch (NamingException e) {
|
||
|
|
// If the getUserByPattern() call fails, try it again with the
|
||
|
|
// credentials of the user that we're searching for
|
||
|
|
try {
|
||
|
|
- userCredentialsAdd(context, dn, credentials);
|
||
|
|
+ userCredentialsAdd(connection.context, dn, credentials);
|
||
|
|
|
||
|
|
- user = getUserByPattern(context, username, attrIds, dn);
|
||
|
|
+ user = getUserByPattern(connection.context, username, attrIds, dn);
|
||
|
|
} finally {
|
||
|
|
- userCredentialsRemove(context);
|
||
|
|
+ userCredentialsRemove(connection.context);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return user;
|
||
|
|
@@ -1636,22 +1619,22 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* information about the user with the specified username, if
|
||
|
|
* found in the directory; otherwise return <code>null</code>.
|
||
|
|
*
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username The username
|
||
|
|
* @param attrIds String[]containing names of attributes to retrieve.
|
||
|
|
* @return the User object
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*/
|
||
|
|
- protected User getUserBySearch(DirContext context,
|
||
|
|
+ protected User getUserBySearch(JNDIConnection connection,
|
||
|
|
String username,
|
||
|
|
String[] attrIds)
|
||
|
|
throws NamingException {
|
||
|
|
|
||
|
|
- if (username == null || userSearchFormat == null)
|
||
|
|
+ if (username == null || connection.userSearchFormat == null)
|
||
|
|
return null;
|
||
|
|
|
||
|
|
// Form the search filter
|
||
|
|
- String filter = userSearchFormat.format(new String[] { username });
|
||
|
|
+ String filter = connection.userSearchFormat.format(new String[] { username });
|
||
|
|
|
||
|
|
// Set up the search controls
|
||
|
|
SearchControls constraints = new SearchControls();
|
||
|
|
@@ -1670,9 +1653,10 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
if (attrIds == null)
|
||
|
|
attrIds = new String[0];
|
||
|
|
constraints.setReturningAttributes(attrIds);
|
||
|
|
+System.out.println("getUserBySearch " + username);
|
||
|
|
|
||
|
|
NamingEnumeration<SearchResult> results =
|
||
|
|
- context.search(userBase, filter, constraints);
|
||
|
|
+ connection.context.search(userBase, filter, constraints);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Fail if no entries found
|
||
|
|
@@ -1693,8 +1677,9 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
// Check no further entries were found
|
||
|
|
try {
|
||
|
|
if (results.hasMore()) {
|
||
|
|
- if(containerLog.isInfoEnabled())
|
||
|
|
- containerLog.info("username " + username + " has multiple entries");
|
||
|
|
+ if (containerLog.isInfoEnabled()) {
|
||
|
|
+ containerLog.info(sm.getString("jndiRealm.multipleEntries", username));
|
||
|
|
+ }
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
} catch (PartialResultException ex) {
|
||
|
|
@@ -1702,7 +1687,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
throw ex;
|
||
|
|
}
|
||
|
|
|
||
|
|
- String dn = getDistinguishedName(context, userBase, result);
|
||
|
|
+ String dn = getDistinguishedName(connection.context, userBase, result);
|
||
|
|
|
||
|
|
if (containerLog.isTraceEnabled())
|
||
|
|
containerLog.trace(" entry found for " + username + " with dn " + dn);
|
||
|
|
@@ -1724,6 +1709,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
|
||
|
|
// Retrieve values of userRoleName attribute
|
||
|
|
ArrayList<String> roles = null;
|
||
|
|
+System.out.println("userRoleName " + userRoleName + " " + attrs.get(userRoleName));
|
||
|
|
if (userRoleName != null)
|
||
|
|
roles = addAttributeValues(userRoleName, attrs, roles);
|
||
|
|
|
||
|
|
@@ -1902,12 +1888,12 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
* a directory search. If no roles are associated with this user,
|
||
|
|
* a zero-length List is returned.
|
||
|
|
*
|
||
|
|
- * @param context The directory context we are searching
|
||
|
|
+ * @param connection The directory context we are searching
|
||
|
|
* @param user The User to be checked
|
||
|
|
* @return the list of role names
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*/
|
||
|
|
- protected List<String> getRoles(DirContext context, User user)
|
||
|
|
+ protected List<String> getRoles(JNDIConnection connection, User user)
|
||
|
|
throws NamingException {
|
||
|
|
|
||
|
|
if (user == null)
|
||
|
|
@@ -1938,11 +1924,11 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
}
|
||
|
|
|
||
|
|
// Are we configured to do role searches?
|
||
|
|
- if ((roleFormat == null) || (roleName == null))
|
||
|
|
+ if ((connection.roleFormat == null) || (roleName == null))
|
||
|
|
return list;
|
||
|
|
|
||
|
|
// Set up parameters for an appropriate search
|
||
|
|
- String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
|
||
|
|
+ String filter = connection.roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
|
||
|
|
SearchControls controls = new SearchControls();
|
||
|
|
if (roleSubtree)
|
||
|
|
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||
|
|
@@ -1951,20 +1937,20 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
controls.setReturningAttributes(new String[] {roleName});
|
||
|
|
|
||
|
|
String base = null;
|
||
|
|
- if (roleBaseFormat != null) {
|
||
|
|
- NameParser np = context.getNameParser("");
|
||
|
|
+ if (connection.roleBaseFormat != null) {
|
||
|
|
+ NameParser np = connection.context.getNameParser("");
|
||
|
|
Name name = np.parse(dn);
|
||
|
|
String nameParts[] = new String[name.size()];
|
||
|
|
for (int i = 0; i < name.size(); i++) {
|
||
|
|
nameParts[i] = name.get(i);
|
||
|
|
}
|
||
|
|
- base = roleBaseFormat.format(nameParts);
|
||
|
|
+ base = connection.roleBaseFormat.format(nameParts);
|
||
|
|
} else {
|
||
|
|
base = "";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Perform the configured search and process the results
|
||
|
|
- NamingEnumeration<SearchResult> results = searchAsUser(context, user, base, filter, controls,
|
||
|
|
+ NamingEnumeration<SearchResult> results = searchAsUser(connection.context, user, base, filter, controls,
|
||
|
|
isRoleSearchAsUser());
|
||
|
|
|
||
|
|
if (results == null)
|
||
|
|
@@ -1977,7 +1963,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
Attributes attrs = result.getAttributes();
|
||
|
|
if (attrs == null)
|
||
|
|
continue;
|
||
|
|
- String dname = getDistinguishedName(context, roleBase, result);
|
||
|
|
+ String dname = getDistinguishedName(connection.context, roleBase, result);
|
||
|
|
String name = getAttributeValue(roleName, attrs);
|
||
|
|
if (name != null && dname != null) {
|
||
|
|
groupMap.put(dname, name);
|
||
|
|
@@ -2010,14 +1996,14 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
Map<String, String> newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
|
||
|
|
|
||
|
|
for (Entry<String, String> group : newGroups.entrySet()) {
|
||
|
|
- filter = roleFormat.format(new String[] { doRFC2254Encoding(group.getKey()),
|
||
|
|
+ filter = connection.roleFormat.format(new String[] { doRFC2254Encoding(group.getKey()),
|
||
|
|
group.getValue(), group.getValue() });
|
||
|
|
|
||
|
|
if (containerLog.isTraceEnabled()) {
|
||
|
|
containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
|
||
|
|
}
|
||
|
|
|
||
|
|
- results = searchAsUser(context, user, roleBase, filter, controls,
|
||
|
|
+ results = searchAsUser(connection.context, user, roleBase, filter, controls,
|
||
|
|
isRoleSearchAsUser());
|
||
|
|
|
||
|
|
try {
|
||
|
|
@@ -2026,7 +2012,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
Attributes attrs = result.getAttributes();
|
||
|
|
if (attrs == null)
|
||
|
|
continue;
|
||
|
|
- String dname = getDistinguishedName(context, roleBase, result);
|
||
|
|
+ String dname = getDistinguishedName(connection.context, roleBase, result);
|
||
|
|
String name = getAttributeValue(roleName, attrs);
|
||
|
|
if (name != null && dname != null && !groupMap.keySet().contains(dname)) {
|
||
|
|
groupMap.put(dname, name);
|
||
|
|
@@ -2169,12 +2155,12 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
/**
|
||
|
|
* Close any open connection to the directory server for this Realm.
|
||
|
|
*
|
||
|
|
- * @param context The directory context to be closed
|
||
|
|
+ * @param connection The directory context to be closed
|
||
|
|
*/
|
||
|
|
- protected void close(DirContext context) {
|
||
|
|
+ protected void close(JNDIConnection connection) {
|
||
|
|
|
||
|
|
// Do nothing if there is no opened connection
|
||
|
|
- if (context == null)
|
||
|
|
+ if (connection.context == null)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Close tls startResponse if used
|
||
|
|
@@ -2189,11 +2175,15 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
try {
|
||
|
|
if (containerLog.isDebugEnabled())
|
||
|
|
containerLog.debug("Closing directory context");
|
||
|
|
- context.close();
|
||
|
|
+ connection.context.close();
|
||
|
|
} catch (NamingException e) {
|
||
|
|
containerLog.error(sm.getString("jndiRealm.close"), e);
|
||
|
|
}
|
||
|
|
- this.context = null;
|
||
|
|
+ connection.context = null;
|
||
|
|
+ // The lock will be reacquired before any manipulation of the connection
|
||
|
|
+ if (connectionPool == null) {
|
||
|
|
+ singleConnectionLock.unlock();
|
||
|
|
+ }
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
@@ -2211,7 +2201,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
- User user = getUser(open(), username, null);
|
||
|
|
+ User user = getUser(get(), username, null);
|
||
|
|
if (user == null) {
|
||
|
|
// User should be found...
|
||
|
|
return null;
|
||
|
|
@@ -2239,20 +2229,20 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
protected Principal getPrincipal(String username,
|
||
|
|
GSSCredential gssCredential) {
|
||
|
|
|
||
|
|
- DirContext context = null;
|
||
|
|
+ JNDIConnection connection = null;
|
||
|
|
Principal principal = null;
|
||
|
|
|
||
|
|
try {
|
||
|
|
|
||
|
|
// Ensure that we have a directory context available
|
||
|
|
- context = open();
|
||
|
|
+ connection = get();
|
||
|
|
|
||
|
|
// Occasionally the directory context will timeout. Try one more
|
||
|
|
// time before giving up.
|
||
|
|
try {
|
||
|
|
|
||
|
|
// Authenticate the specified username if possible
|
||
|
|
- principal = getPrincipal(context, username, gssCredential);
|
||
|
|
+ principal = getPrincipal(connection, username, gssCredential);
|
||
|
|
|
||
|
|
} catch (CommunicationException | ServiceUnavailableException e) {
|
||
|
|
|
||
|
|
@@ -2260,20 +2250,19 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
|
||
|
|
|
||
|
|
// close the connection so we know it will be reopened.
|
||
|
|
- if (context != null)
|
||
|
|
- close(context);
|
||
|
|
+ close(connection);
|
||
|
|
|
||
|
|
// open a new directory context.
|
||
|
|
- context = open();
|
||
|
|
+ connection = get();
|
||
|
|
|
||
|
|
// Try the authentication again.
|
||
|
|
- principal = getPrincipal(context, username, gssCredential);
|
||
|
|
+ principal = getPrincipal(connection, username, gssCredential);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Release this context
|
||
|
|
- release(context);
|
||
|
|
+ release(connection);
|
||
|
|
|
||
|
|
// Return the authenticated Principal (if any)
|
||
|
|
return principal;
|
||
|
|
@@ -2284,8 +2273,7 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
containerLog.error(sm.getString("jndiRealm.exception"), e);
|
||
|
|
|
||
|
|
// Close the connection so that it gets reopened next time
|
||
|
|
- if (context != null)
|
||
|
|
- close(context);
|
||
|
|
+ close(connection);
|
||
|
|
|
||
|
|
// Return "not authenticated" for this request
|
||
|
|
return null;
|
||
|
|
@@ -2298,19 +2286,20 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the principal associated with the specified certificate.
|
||
|
|
- * @param context The directory context
|
||
|
|
+ * @param connection The directory context
|
||
|
|
* @param username The user name
|
||
|
|
* @param gssCredential The credentials
|
||
|
|
* @return the Principal associated with the given certificate.
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*/
|
||
|
|
- protected synchronized Principal getPrincipal(DirContext context,
|
||
|
|
+ protected Principal getPrincipal(JNDIConnection connection,
|
||
|
|
String username, GSSCredential gssCredential)
|
||
|
|
throws NamingException {
|
||
|
|
|
||
|
|
User user = null;
|
||
|
|
List<String> roles = null;
|
||
|
|
Hashtable<?, ?> preservedEnvironment = null;
|
||
|
|
+ DirContext context = connection.context;
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (gssCredential != null && isUseDelegatedCredential()) {
|
||
|
|
@@ -2326,9 +2315,9 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
// Note: Subject already set in SPNEGO authenticator so no need
|
||
|
|
// for Subject.doAs() here
|
||
|
|
}
|
||
|
|
- user = getUser(context, username);
|
||
|
|
+ user = getUser(connection, username);
|
||
|
|
if (user != null) {
|
||
|
|
- roles = getRoles(context, user);
|
||
|
|
+ roles = getRoles(connection, user);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
restoreEnvironmentParameter(context,
|
||
|
|
@@ -2363,50 +2352,100 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
/**
|
||
|
|
* Open (if necessary) and return a connection to the configured
|
||
|
|
* directory server for this Realm.
|
||
|
|
- * @return the directory context
|
||
|
|
+ * @return the connection
|
||
|
|
* @exception NamingException if a directory server error occurs
|
||
|
|
*/
|
||
|
|
- protected DirContext open() throws NamingException {
|
||
|
|
+ protected JNDIConnection get() throws NamingException {
|
||
|
|
+ JNDIConnection connection = null;
|
||
|
|
+ // Use the pool if available, otherwise use the single connection
|
||
|
|
+ if (connectionPool != null) {
|
||
|
|
+ connection = connectionPool.pop();
|
||
|
|
+ if (connection == null) {
|
||
|
|
+ connection = create();
|
||
|
|
+ }
|
||
|
|
+ } else {
|
||
|
|
+ singleConnectionLock.lock();
|
||
|
|
+ connection = singleConnection;
|
||
|
|
+ }
|
||
|
|
+ if (connection.context == null) {
|
||
|
|
+ open(connection);
|
||
|
|
+ }
|
||
|
|
+ return connection;
|
||
|
|
+ }
|
||
|
|
|
||
|
|
- // Do nothing if there is a directory server connection already open
|
||
|
|
- if (context != null)
|
||
|
|
- return context;
|
||
|
|
+ /**
|
||
|
|
+ * Release our use of this connection so that it can be recycled.
|
||
|
|
+ *
|
||
|
|
+ * @param connection The directory context to release
|
||
|
|
+ */
|
||
|
|
+ protected void release(JNDIConnection connection) {
|
||
|
|
+ if (connectionPool != null) {
|
||
|
|
+ if (!connectionPool.push(connection)) {
|
||
|
|
+ // Any connection that doesn't end back to the pool must be closed
|
||
|
|
+ close(connection);
|
||
|
|
+ }
|
||
|
|
+ } else {
|
||
|
|
+ singleConnectionLock.unlock();
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
|
||
|
|
- try {
|
||
|
|
+ /**
|
||
|
|
+ * Create a new connection wrapper, along with the
|
||
|
|
+ * message formats.
|
||
|
|
+ * @return the new connection
|
||
|
|
+ */
|
||
|
|
+ protected JNDIConnection create() {
|
||
|
|
+ JNDIConnection connection = new JNDIConnection();
|
||
|
|
+ if (userSearch != null) {
|
||
|
|
+ connection.userSearchFormat = new MessageFormat(userSearch);
|
||
|
|
+ }
|
||
|
|
+ if (userPattern != null) {
|
||
|
|
+ int len = userPatternArray.length;
|
||
|
|
+ connection.userPatternFormatArray = new MessageFormat[len];
|
||
|
|
+ for (int i = 0; i < len; i++) {
|
||
|
|
+ connection.userPatternFormatArray[i] =
|
||
|
|
+ new MessageFormat(userPatternArray[i]);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ if (roleBase != null) {
|
||
|
|
+ connection.roleBaseFormat = new MessageFormat(roleBase);
|
||
|
|
+ }
|
||
|
|
+ if (roleSearch != null) {
|
||
|
|
+ connection.roleFormat = new MessageFormat(roleSearch);
|
||
|
|
+ }
|
||
|
|
+ return connection;
|
||
|
|
+ }
|
||
|
|
|
||
|
|
+ /**
|
||
|
|
+ * Create a new connection to the directory server.
|
||
|
|
+ * @param connection The directory server connection wrapper
|
||
|
|
+ * @throws NamingException if a directory server error occurs
|
||
|
|
+ */
|
||
|
|
+ protected void open(JNDIConnection connection) throws NamingException {
|
||
|
|
+ try {
|
||
|
|
// Ensure that we have a directory context available
|
||
|
|
- context = createDirContext(getDirectoryContextEnvironment());
|
||
|
|
-
|
||
|
|
+ connection.context = createDirContext(getDirectoryContextEnvironment());
|
||
|
|
} catch (Exception e) {
|
||
|
|
if (alternateURL == null || alternateURL.length() == 0) {
|
||
|
|
// No alternate URL. Re-throw the exception.
|
||
|
|
throw e;
|
||
|
|
}
|
||
|
|
-
|
||
|
|
connectionAttempt = 1;
|
||
|
|
-
|
||
|
|
// log the first exception.
|
||
|
|
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
|
||
|
|
-
|
||
|
|
// Try connecting to the alternate url.
|
||
|
|
- context = createDirContext(getDirectoryContextEnvironment());
|
||
|
|
-
|
||
|
|
+ connection.context = createDirContext(getDirectoryContextEnvironment());
|
||
|
|
} finally {
|
||
|
|
-
|
||
|
|
// reset it in case the connection times out.
|
||
|
|
// the primary may come back.
|
||
|
|
connectionAttempt = 0;
|
||
|
|
-
|
||
|
|
}
|
||
|
|
-
|
||
|
|
- return context;
|
||
|
|
-
|
||
|
|
}
|
||
|
|
|
||
|
|
@Override
|
||
|
|
public boolean isAvailable() {
|
||
|
|
// Simple best effort check
|
||
|
|
- return (context != null);
|
||
|
|
+ return (connectionPool != null || singleConnection.context != null);
|
||
|
|
}
|
||
|
|
|
||
|
|
private DirContext createDirContext(Hashtable<String, String> env) throws NamingException {
|
||
|
|
@@ -2559,18 +2598,6 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
- /**
|
||
|
|
- * Release our use of this connection so that it can be recycled.
|
||
|
|
- *
|
||
|
|
- * @param context The directory context to release
|
||
|
|
- */
|
||
|
|
- protected void release(DirContext context) {
|
||
|
|
-
|
||
|
|
- // NO-OP since we are not pooling anything
|
||
|
|
-
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
-
|
||
|
|
// ------------------------------------------------------ Lifecycle Methods
|
||
|
|
|
||
|
|
|
||
|
|
@@ -2585,15 +2612,22 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
@Override
|
||
|
|
protected void startInternal() throws LifecycleException {
|
||
|
|
|
||
|
|
+ if (connectionPoolSize != 1) {
|
||
|
|
+ connectionPool = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, connectionPoolSize);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
// Check to see if the connection to the directory can be opened
|
||
|
|
+ JNDIConnection connection = null;
|
||
|
|
try {
|
||
|
|
- open();
|
||
|
|
+ connection = get();
|
||
|
|
} catch (NamingException e) {
|
||
|
|
// A failure here is not fatal as the directory may be unavailable
|
||
|
|
// now but available later. Unavailability of the directory is not
|
||
|
|
// fatal once the Realm has started so there is no reason for it to
|
||
|
|
// be fatal when the Realm starts.
|
||
|
|
containerLog.error(sm.getString("jndiRealm.open"), e);
|
||
|
|
+ } finally {
|
||
|
|
+ release(connection);
|
||
|
|
}
|
||
|
|
|
||
|
|
super.startInternal();
|
||
|
|
@@ -2610,12 +2644,18 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
*/
|
||
|
|
@Override
|
||
|
|
protected void stopInternal() throws LifecycleException {
|
||
|
|
-
|
||
|
|
super.stopInternal();
|
||
|
|
-
|
||
|
|
// Close any open directory server connection
|
||
|
|
- close(this.context);
|
||
|
|
-
|
||
|
|
+ if (connectionPool == null) {
|
||
|
|
+ singleConnectionLock.lock();
|
||
|
|
+ close(singleConnection);
|
||
|
|
+ } else {
|
||
|
|
+ JNDIConnection connection = null;
|
||
|
|
+ while ((connection = connectionPool.pop()) != null) {
|
||
|
|
+ close(connection);
|
||
|
|
+ }
|
||
|
|
+ connectionPool = null;
|
||
|
|
+ }
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
@@ -2812,5 +2852,43 @@ public class JNDIRealm extends RealmBase {
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Class holding the connection to the directory plus the associated
|
||
|
|
+ * non thread safe message formats.
|
||
|
|
+ */
|
||
|
|
+ protected static class JNDIConnection {
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The MessageFormat object associated with the current
|
||
|
|
+ * <code>userSearch</code>.
|
||
|
|
+ */
|
||
|
|
+ protected MessageFormat userSearchFormat = null;
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * An array of MessageFormat objects associated with the current
|
||
|
|
+ * <code>userPatternArray</code>.
|
||
|
|
+ */
|
||
|
|
+ protected MessageFormat[] userPatternFormatArray = null;
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The MessageFormat object associated with the current
|
||
|
|
+ * <code>roleBase</code>.
|
||
|
|
+ */
|
||
|
|
+ protected MessageFormat roleBaseFormat = null;
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The MessageFormat object associated with the current
|
||
|
|
+ * <code>roleSearch</code>.
|
||
|
|
+ */
|
||
|
|
+ protected MessageFormat roleFormat = null;
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The directory context linking us to our directory server.
|
||
|
|
+ */
|
||
|
|
+ protected DirContext context = null;
|
||
|
|
+
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
}
|
||
|
|
|
||
|
|
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties b/java/org/apache/catalina/realm/LocalStrings.properties
|
||
|
|
index 1a96cc4..b66ad94 100644
|
||
|
|
--- a/java/org/apache/catalina/realm/LocalStrings.properties
|
||
|
|
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
|
||
|
|
@@ -48,6 +48,7 @@ jndiRealm.exception.retry=Exception performing authentication. Retrying...
|
||
|
|
jndiRealm.invalidHostnameVerifier=[{0}] not a valid class name for a HostnameVerifier
|
||
|
|
jndiRealm.invalidSslProtocol=Given protocol [{0}] is invalid. It has to be one of [{1}]
|
||
|
|
jndiRealm.invalidSslSocketFactory=[{0}] not a valid class name for a SSLSocketFactory
|
||
|
|
+jndiRealm.multipleEntries=User name [{0}] has multiple entries
|
||
|
|
jndiRealm.negotiatedTls=Negotiated tls connection using protocol [{0}]
|
||
|
|
jndiRealm.open=Exception opening directory server connection
|
||
|
|
jndiRealm.tlsClose=Exception closing tls response
|
||
|
|
@@ -93,4 +94,4 @@ lockOutRealm.removeWarning=User [{0}] was removed from the failed users cache af
|
||
|
|
credentialHandler.invalidStoredCredential=The invalid stored credential string [{0}] was provided by the Realm to match with the user provided credentials
|
||
|
|
credentialHandler.unableToMutateUserCredential=Failed to mutate user provided credentials. This typically means the CredentialHandler configuration is invalid
|
||
|
|
mdCredentialHandler.unknownEncoding=The encoding [{0}] is not supported so the current setting of [{1}] will still be used
|
||
|
|
-pbeCredentialHandler.invalidKeySpec=Unable to generate a password based key
|
||
|
|
\ No newline at end of file
|
||
|
|
+pbeCredentialHandler.invalidKeySpec=Unable to generate a password based key
|
||
|
|
diff --git a/test/org/apache/catalina/realm/TestJNDIRealm.java b/test/org/apache/catalina/realm/TestJNDIRealm.java
|
||
|
|
index b2a82e4..238106e 100644
|
||
|
|
--- a/test/org/apache/catalina/realm/TestJNDIRealm.java
|
||
|
|
+++ b/test/org/apache/catalina/realm/TestJNDIRealm.java
|
||
|
|
@@ -117,9 +117,12 @@ public class TestJNDIRealm {
|
||
|
|
realm.setContainer(context);
|
||
|
|
realm.setUserSearch("");
|
||
|
|
|
||
|
|
- Field field = JNDIRealm.class.getDeclaredField("context");
|
||
|
|
+ // Usually everything is created in create() but that's not the case here
|
||
|
|
+ Field field = JNDIRealm.class.getDeclaredField("singleConnection");
|
||
|
|
field.setAccessible(true);
|
||
|
|
- field.set(realm, mockDirContext(mockSearchResults(password)));
|
||
|
|
+ Field field2 = JNDIRealm.JNDIConnection.class.getDeclaredField("context");
|
||
|
|
+ field2.setAccessible(true);
|
||
|
|
+ field2.set(field.get(realm), mockDirContext(mockSearchResults(password)));
|
||
|
|
|
||
|
|
realm.start();
|
||
|
|
|
||
|
|
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
|
||
|
|
index 7bcc3d9..173c209 100644
|
||
|
|
--- a/webapps/docs/changelog.xml
|
||
|
|
+++ b/webapps/docs/changelog.xml
|
||
|
|
@@ -60,6 +60,9 @@
|
||
|
|
searching for nested groups when the JNDIRealm is configured with
|
||
|
|
<code>roleNested</code> set to <code>true</code>. (markt)
|
||
|
|
</fix>
|
||
|
|
+ <update>
|
||
|
|
+ Add connection pooling to JNDI realm. (remm)
|
||
|
|
+ </update>
|
||
|
|
</changelog>
|
||
|
|
</subsection>
|
||
|
|
</section>
|
||
|
|
diff --git a/webapps/docs/config/realm.xml b/webapps/docs/config/realm.xml
|
||
|
|
index 1d5ae6e..a4bc5ef 100644
|
||
|
|
--- a/webapps/docs/config/realm.xml
|
||
|
|
+++ b/webapps/docs/config/realm.xml
|
||
|
|
@@ -433,6 +433,13 @@
|
||
|
|
property.</p>
|
||
|
|
</attribute>
|
||
|
|
|
||
|
|
+ <attribute name="connectionPoolSize" required="false">
|
||
|
|
+ <p>The JNDI realm can use a pool of connections to the directory server
|
||
|
|
+ to avoid blocking on a single connection. This attribute value is the
|
||
|
|
+ maximum pool size. If not specified, it will use <code>1</code>, which
|
||
|
|
+ means a single connection will be used.</p>
|
||
|
|
+ </attribute>
|
||
|
|
+
|
||
|
|
<attribute name="connectionTimeout" required="false">
|
||
|
|
<p>The timeout in milliseconds to use when establishing the connection
|
||
|
|
to the LDAP directory. If not specified, a value of 5000 (5 seconds) is
|
||
|
|
--
|
||
|
|
2.23.0
|
||
|
|
|