/** *{@inheritDoc} *<p> *IfthereareanyerrorswiththeJNDIconnection,executingthequeryoranythingwereturnnull(don't *authenticate).Thiseventisalsologged,andtheconnectionwillbeclosedsothatasubsequentrequestwill *automaticallyre-openit.
*/
@Override public Principal authenticate(String username, String credentials) {
ClassLoader ocl = null; Thread currentThread = null;
JNDIConnection connection = null;
Principal principal = null;
try { // https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 // This can move back to open() once it is known that Tomcat must be // running on a JVM that includes a fix for // https://bugs.openjdk.java.net/browse/JDK-8273874 if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
}
// Ensure that we have a directory context available
connection = get();
try {
// Occasionally the directory context will timeout. Try one more // time before giving up.
// Authenticate the specified username if possible
principal = authenticate(connection, username, credentials);
if (username == null || username.equals("") || credentials == null || credentials.equals("")) { if (containerLog.isDebugEnabled()) {
containerLog.debug("username null or empty: returning null principal.");
} returnnull;
}
ClassLoader ocl = null; Thread currentThread = null; try { // https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 // This can move back to open() once it is known that Tomcat must be // running on a JVM that includes a fix for // https://bugs.openjdk.java.net/browse/JDK-8273874 if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
}
if (userPatternArray != null) { for (int curUserPattern = 0; curUserPattern < userPatternArray.length; curUserPattern++) { // Retrieve user information
User user = getUser(connection, username, credentials, curUserPattern); if (user != null) { try { // Check the user's credentials if (checkCredentials(connection.context, user, credentials)) { // Search for additional roles
List<String> roles = getRoles(connection, user); if (containerLog.isDebugEnabled()) {
containerLog.debug("Found roles: " + ((roles == null) ? "" : roles.toString()));
} returnnew GenericPrincipal(username, roles);
}
} catch (InvalidNameException ine) { // Log the problem for posterity
containerLog.warn(sm.getString("jndiRealm.exception"), ine); // ignore; this is probably due to a name not fitting // the search path format exactly, as in a fully- // qualified name being munged into a search path // that already contains cn= or vice-versa
}
}
} returnnull;
} else { // Retrieve user information
User user = getUser(connection, username, credentials); if (user == null) { returnnull;
}
// Check the user's credentials if (!checkCredentials(connection.context, user, credentials)) { returnnull;
}
// Create and return a suitable Principal for this user returnnew GenericPrincipal(username, roles);
}
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
/* *https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved *backtoopen()onceitisknownthatTomcatmustberunningonaJVMthatincludesafixfor *https://bugs.openjdk.java.net/browse/JDK-8273874
*/
@Override public Principal authenticate(String username) {
ClassLoader ocl = null; Thread currentThread = null; try { if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
} returnsuper.authenticate(username);
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
/* *https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved *backtoopen()onceitisknownthatTomcatmustberunningonaJVMthatincludesafixfor *https://bugs.openjdk.java.net/browse/JDK-8273874
*/
@Override public Principal authenticate(String username, String clientDigest, String nonce, String nc, String cnonce,
String qop, String realm, String digestA2, String algorithm) {
ClassLoader ocl = null; Thread currentThread = null; try { if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
} returnsuper.authenticate(username, clientDigest, nonce, nc, cnonce, qop, realm, digestA2, algorithm);
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
/* *https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved *backtoopen()onceitisknownthatTomcatmustberunningonaJVMthatincludesafixfor *https://bugs.openjdk.java.net/browse/JDK-8273874
*/
@Override public Principal authenticate(X509Certificate[] certs) {
ClassLoader ocl = null; Thread currentThread = null; try { if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
} returnsuper.authenticate(certs);
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
/* *https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved *backtoopen()onceitisknownthatTomcatmustberunningonaJVMthatincludesafixfor *https://bugs.openjdk.java.net/browse/JDK-8273874
*/
@Override public Principal authenticate(GSSContext gssContext, boolean storeCred) {
ClassLoader ocl = null; Thread currentThread = null; try { if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
} returnsuper.authenticate(gssContext, storeCred);
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
/* *https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved *backtoopen()onceitisknownthatTomcatmustberunningonaJVMthatincludesafixfor *https://bugs.openjdk.java.net/browse/JDK-8273874
*/
@Override public Principal authenticate(GSSName gssName, GSSCredential gssCredential) {
ClassLoader ocl = null; Thread currentThread = null; try { if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
} returnsuper.authenticate(gssName, gssCredential);
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
// Get attributes to retrieve from user entry
List<String> list = new ArrayList<>(); if (userPassword != null) {
list.add(userPassword);
} if (userRoleName != null) {
list.add(userRoleName);
} if (userRoleAttribute != null) {
list.add(userRoleAttribute);
}
String[] attrIds = list.toArray(new String[0]);
// Use pattern or search for user entry if (userPatternArray != null && curUserPattern >= 0) {
user = getUserByPattern(connection, username, credentials, attrIds, curUserPattern); if (containerLog.isDebugEnabled()) {
containerLog.debug("Found user by pattern [" + user + "]");
}
} else { boolean thisUserSearchAsUser = isUserSearchAsUser(); try { if (thisUserSearchAsUser) {
userCredentialsAdd(connection.context, username, credentials);
}
user = getUserBySearch(connection, username, attrIds);
} finally { if (thisUserSearchAsUser) {
userCredentialsRemove(connection.context);
}
} if (containerLog.isDebugEnabled()) {
containerLog.debug("Found user by search [" + user + "]");
}
} if (userPassword == null && credentials != null && user != null) { // The password is available. Insert it since it may be required for // role searches. returnnew User(user.getUserName(), user.getDN(), credentials, user.getRoles(), user.getUserRoleId());
}
// If no attributes are requested, no need to look for them if (attrIds == null || attrIds.length == 0) { returnnew User(username, dn, null, null, null);
}
// Get required attributes from user entry
Attributes attrs = null; try {
attrs = context.getAttributes(dn, attrIds);
} catch (NameNotFoundException e) { returnnull;
} if (attrs == null) { returnnull;
}
// Retrieve value of userPassword
String password = null; if (userPassword != null) {
password = getAttributeValue(userPassword, attrs);
}
// Form the DistinguishedName from the user pattern. // Escape in case username contains a character with special meaning in // an attribute value.
String dn = connection.userPatternFormatArray[curUserPattern]
.format(new String[] { doAttributeValueEscaping(username) });
try {
user = getUserByPattern(connection.context, username, attrIds, dn);
} catch (NameNotFoundException e) { returnnull;
} catch (NamingException e) { // If the getUserByPattern() call fails, try it again with the // credentials of the user that we're searching for try {
userCredentialsAdd(connection.context, dn, credentials);
// Form the search filter // Escape in case username contains a character with special meaning in // a search filter.
String filter = connection.userSearchFormat.format(new String[] { doFilterEscaping(username) });
// Set up the search controls
SearchControls constraints = new SearchControls();
if (userSubtree) {
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
} else {
constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
}
try { // Fail if no entries found try { if (results == null || !results.hasMore()) { returnnull;
}
} catch (PartialResultException ex) { if (!adCompat) { throw ex;
} else { returnnull;
}
}
// Get result for the first entry found
SearchResult result = results.next();
// Check no further entries were found try { if (results.hasMore()) { if (containerLog.isInfoEnabled()) {
containerLog.info(sm.getString("jndiRealm.multipleEntries", username));
} returnnull;
}
} catch (PartialResultException ex) { if (!adCompat) { throw ex;
}
}
if (credentials == null || user == null) { returnfalse;
}
// This is returned from the directory so will be attribute value // escaped if required
String dn = user.getDN(); if (dn == null) { returnfalse;
}
// Validate the credentials specified by the user if (containerLog.isTraceEnabled()) {
containerLog.trace(" validating credentials by binding as the user");
}
// This is returned from the directory so will be attribute value // escaped if required
String dn = user.getDN(); // This is the name the user provided to the authentication process so // it will not be escaped
String username = user.getUserName();
String userRoleId = user.getUserRoleId();
if (containerLog.isTraceEnabled()) {
containerLog.trace(" getRoles(" + dn + ")");
}
// Start with roles retrieved from the user entry
List<String> list = new ArrayList<>();
List<String> userRoles = user.getRoles(); if (userRoles != null) {
list.addAll(userRoles);
} if (commonRole != null) {
list.add(commonRole);
}
if (containerLog.isTraceEnabled()) {
containerLog.trace(" Found " + list.size() + " user internal roles");
containerLog.trace(" Found user internal roles " + list.toString());
}
// Are we configured to do role searches? if (connection.roleFormat == null || roleName == null) { return list;
}
// Set up parameters for an appropriate search filter // The dn is already attribute value escaped but the others are not // This is a filter so all input will require filter escaping
String filter = connection.roleFormat
.format(new String[] { doFilterEscaping(dn), doFilterEscaping(doAttributeValueEscaping(username)),
doFilterEscaping(doAttributeValueEscaping(userRoleId)) });
SearchControls controls = new SearchControls(); if (roleSubtree) {
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
} else {
controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
}
controls.setReturningAttributes(new String[] { roleName });
String base = null; 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++) { // May have been returned with \<char> escaping rather than // \<hex><hex>. Make sure it is \<hex><hex>.
nameParts[i] = convertToHexEscape(name.get(i));
}
base = connection.roleBaseFormat.format(nameParts);
} else {
base = "";
}
// Perform the configured search and process the results
NamingEnumeration<SearchResult> results =
searchAsUser(connection.context, user, base, filter, controls, isRoleSearchAsUser());
if (results == null) { return list; // Should never happen, but just in case ...
}
Map<String,String> groupMap = new HashMap<>(); try { while (results.hasMore()) {
SearchResult result = results.next();
Attributes attrs = result.getAttributes(); if (attrs == null) { continue;
}
String dname = getDistinguishedName(connection.context, base, result);
String name = getAttributeValue(roleName, attrs); if (name != null && dname != null) {
groupMap.put(dname, name);
}
}
} catch (PartialResultException ex) { if (!adCompat) { throw ex;
}
} finally {
results.close();
}
if (containerLog.isTraceEnabled()) {
Set<Entry<String,String>> entries = groupMap.entrySet();
containerLog.trace(" Found " + entries.size() + " direct roles"); for (Entry<String,String> entry : entries) {
containerLog.trace(" Found direct role " + entry.getKey() + " -> " + entry.getValue());
}
}
// if nested group search is enabled, perform searches for nested groups until no new group is found if (getRoleNested()) {
// The following efficient algorithm is known as memberOf Algorithm, as described in "Practices in // Directory Groups". It avoids group slurping and handles cyclic group memberships as well. // See http://middleware.internet2.edu/dir/ for details
Map<String,String> newGroups = new HashMap<>(groupMap); while (!newGroups.isEmpty()) {
Map<String,String> newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
for (Entry<String,String> group : newGroups.entrySet()) { // Group key is already value escaped if required // Group value is not value escaped // Everything needs to be filter escaped
filter = connection.roleFormat.format(new String[] { doFilterEscaping(group.getKey()),
doFilterEscaping(doAttributeValueEscaping(group.getValue())),
doFilterEscaping(doAttributeValueEscaping(group.getValue())) });
if (containerLog.isTraceEnabled()) {
containerLog
.trace("Perform a nested group search with base " + roleBase + " and filter " + filter);
}
// Do nothing if there is no opened connection if (connection == null || connection.context == null) { if (connectionPool == null) {
singleConnectionLock.unlock();
} return;
}
// Close tls startResponse if used if (tls != null) { try {
tls.close();
} catch (IOException e) {
containerLog.error(sm.getString("jndiRealm.tlsClose"), e);
}
} // Close our opened connection try { if (containerLog.isDebugEnabled()) {
containerLog.debug("Closing directory context");
}
connection.context.close();
} catch (NamingException e) {
containerLog.error(sm.getString("jndiRealm.close"), e);
}
connection.context = null; // The lock will be reacquired before any manipulation of the connection if (connectionPool == null) {
singleConnectionLock.unlock();
}
}
/** *Closeallpooledconnections.
*/ protectedvoid closePooledConnections() { if (connectionPool != null) { // Close any pooled connections as they might be bad as well synchronized (connectionPool) {
JNDIConnection connection = null; while ((connection = connectionPool.pop()) != null) {
close(connection);
}
}
}
}
JNDIConnection connection = null;
User user = null; try { // Ensure that we have a directory context available
connection = get();
// Occasionally the directory context will timeout. Try one more // time before giving up. try {
user = getUser(connection, username, null);
} catch (NullPointerException | NamingException e) { // log the exception so we know it's there.
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
// close the connection so we know it will be reopened.
close(connection);
closePooledConnections();
// open a new directory context.
connection = get();
// Try the authentication again.
user = getUser(connection, username, null);
}
// Release this context
release(connection);
if (user == null) { // User should be found... returnnull;
} else { // ... and have a password return user.getPassword();
}
} catch (Exception e) { // Log the problem for posterity
containerLog.error(sm.getString("jndiRealm.exception"), e); // close the connection so we know it will be reopened.
close(connection);
closePooledConnections(); returnnull;
}
}
/** *Open(ifnecessary)andreturnaconnectiontotheconfigureddirectoryserverforthisRealm. * *@returntheconnection * *@exceptionNamingExceptionifadirectoryservererroroccurs
*/ 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(); if (singleConnection == null) {
singleConnection = create();
}
connection = singleConnection;
} if (connection.context == null) {
open(connection);
} return connection;
}
/** *Releaseouruseofthisconnectionsothatitcanberecycled. * *@paramconnectionThedirectorycontexttorelease
*/ protectedvoid release(JNDIConnection connection) { if (connectionPool != null) { if (connection != null) { if (!connectionPool.push(connection)) { // Any connection that doesn't end back to the pool must be closed
close(connection);
}
}
} else {
singleConnectionLock.unlock();
}
}
/** *Createanewconnectiontothedirectoryserver. * *@paramconnectionThedirectoryserverconnectionwrapper * *@throwsNamingExceptionifadirectoryservererroroccurs
*/ protectedvoid open(JNDIConnection connection) throws NamingException { try { // Ensure that we have a directory context available
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.
connection.context = createDirContext(getDirectoryContextEnvironment());
} finally { // reset it in case the connection times out. // the primary may come back.
connectionAttempt = 0;
}
}
if (connectionPoolSize != 1) {
connectionPool = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, connectionPoolSize);
}
// Check to see if the connection to the directory can be opened
ClassLoader ocl = null; Thread currentThread = null;
JNDIConnection connection = null; try { // https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 // This can move back to open() once it is known that Tomcat must be // running on a JVM that includes a fix for // https://bugs.openjdk.java.net/browse/JDK-8273874 if (!isUseContextClassLoader()) {
currentThread = Thread.currentThread();
ocl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(this.getClass().getClassLoader());
}
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); if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
super.startInternal();
}
/** *Gracefullyterminatetheactiveuseofthepublicmethodsofthiscomponentandimplementtherequirementsof *{@linkorg.apache.catalina.util.LifecycleBase#stopInternal()}. * *@exceptionLifecycleExceptionifthiscomponentdetectsafatalerrorthatneedstobereported
*/
@Override protectedvoid stopInternal() throws LifecycleException { super.stopInternal(); // Close any open directory server connection if (connectionPool == null) {
singleConnectionLock.lock();
close(singleConnection);
} else {
closePooledConnections();
connectionPool = null;
}
}
/** *Returnsthedistinguishednameofasearchresult. * *@paramcontextOurDirContext *@parambaseThebaseDN *@paramresultThesearchresult * *@returnStringcontainingthedistinguishedname * *@exceptionNamingExceptionifadirectoryservererroroccurs
*/ protected String getDistinguishedName(DirContext context, String base, SearchResult result) throws NamingException { // Get the entry's distinguished name. For relative results, this means // we need to composite a name with the base name, the context name, and // the result name. For non-relative names, use the returned name.
String resultName = result.getName();
Name name; if (result.isRelative()) { if (containerLog.isTraceEnabled()) {
containerLog.trace(" search returned relative name: " + resultName);
}
NameParser parser = context.getNameParser("");
Name contextName = parser.parse(context.getNameInNamespace());
Name baseName = parser.parse(base);
// Bugzilla 32269
Name entryName = parser.parse(new CompositeName(resultName).get(0));
name = contextName.addAll(baseName);
name = name.addAll(entryName);
} else { if (containerLog.isTraceEnabled()) {
containerLog.trace(" search returned absolute name: " + resultName);
} try { // Normalize the name by running it through the name parser.
NameParser parser = context.getNameParser("");
URI userNameUri = new URI(resultName);
String pathComponent = userNameUri.getPath(); // Should not ever have an empty path component, since that is /{DN} if (pathComponent.length() < 1) { thrownew InvalidNameException(sm.getString("jndiRealm.invalidName", resultName));
}
name = parser.parse(pathComponent.substring(1));
} catch (URISyntaxException e) { thrownew InvalidNameException(sm.getString("jndiRealm.invalidName", resultName));
}
}
protectedstatic String convertToHexEscape(String input) { if (input.indexOf('\\') == -1) { // No escaping present. Return original. return input;
}
// +6 allows for 3 escaped characters by default
StringBuilder result = new StringBuilder(input.length() + 6); boolean previousSlash = false; for (int i = 0; i < input.length(); i++) { char c = input.charAt(i);
public JNDIConnection(String userSearch, String[] userPatternArray, String roleBase, String roleSearch) { if (userSearch == null) {
userSearchFormat = null;
} else {
userSearchFormat = new MessageFormat(userSearch);
}
if (userPatternArray == null) {
userPatternFormatArray = null;
} else { int len = userPatternArray.length;
userPatternFormatArray = new MessageFormat[len]; for (int i = 0; i < len; i++) {
userPatternFormatArray[i] = new MessageFormat(userPatternArray[i]);
}
}
if (roleBase == null) {
roleBaseFormat = null;
} else {
roleBaseFormat = new MessageFormat(roleBase);
}
if (roleSearch == null) {
roleFormat = null;
} else {
roleFormat = new MessageFormat(roleSearch);
}
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.333 Sekunden
(vorverarbeitet am 2026-10-04)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.