Impressum JNDIRealm.java
Interaktion und PortierbarkeitJAVA
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.catalina.realm;
/** * <p> * Implementation of <strong>Realm</strong> that works with a directory server accessed via the Java Naming and * Directory Interface (JNDI) APIs. The following constraints are imposed on the data structure in the underlying * directory server: * </p> * <ul> * <li>Each user that can be authenticated is represented by an individual element in the top level * <code>DirContext</code> that is accessed via the <code>connectionURL</code> property.</li> * <li>If a socket connection cannot be made to the <code>connectURL</code> an attempt will be made to use the * <code>alternateURL</code> if it exists.</li> * <li>Each user element has a distinguished name that can be formed by substituting the presented username into a * pattern configured by the <code>userPattern</code> property.</li> * <li>Alternatively, if the <code>userPattern</code> property is not specified, a unique element can be located by * searching the directory context. In this case: * <ul> * <li>The <code>userSearch</code> pattern specifies the search filter after substitution of the username.</li> * <li>The <code>userBase</code> property can be set to the element that is the base of the subtree containing users. If * not specified, the search base is the top-level context.</li> * <li>The <code>userSubtree</code> property can be set to <code>true</code> if you wish to search the entire subtree of * the directory context. The default value of <code>false</code> requests a search of only the current level.</li> * </ul> * </li> * <li>The user may be authenticated by binding to the directory with the username and password presented. This method * is used when the <code>userPassword</code> property is not specified.</li> * <li>The user may be authenticated by retrieving the value of an attribute from the directory and comparing it * explicitly with the value presented by the user. This method is used when the <code>userPassword</code> property is * specified, in which case: * <ul> * <li>The element for this user must contain an attribute named by the <code>userPassword</code> property. * <li>The value of the user password attribute is either a cleartext String, or the result of passing a cleartext * String through the <code>RealmBase.digest()</code> method (using the standard digest support included in * <code>RealmBase</code>). * <li>The user is considered to be authenticated if the presented credentials (after being passed through * <code>RealmBase.digest()</code>) are equal to the retrieved value for the user password attribute.</li> * </ul> * </li> * <li>Each group of users that has been assigned a particular role may be represented by an individual element in the * top level <code>DirContext</code> that is accessed via the <code>connectionURL</code> property. This element has the * following characteristics: * <ul> * <li>The set of all possible groups of interest can be selected by a search pattern configured by the * <code>roleSearch</code> property.</li> * <li>The <code>roleSearch</code> pattern optionally includes pattern replacements "{0}" for the distinguished name, * and/or "{1}" for the username, and/or "{2}" the value of an attribute from the user's directory entry (the attribute * is specified by the <code>userRoleAttribute</code> property), of the authenticated user for which roles will be * retrieved.</li> * <li>The <code>roleBase</code> property can be set to the element that is the base of the search for matching roles. * If not specified, the entire context will be searched.</li> * <li>The <code>roleSubtree</code> property can be set to <code>true</code> if you wish to search the entire subtree of * the directory context. The default value of <code>false</code> requests a search of only the current level.</li> * <li>The element includes an attribute (whose name is configured by the <code>roleName</code> property) containing the * name of the role represented by this element.</li> * </ul> * </li> * <li>In addition, roles may be represented by the values of an attribute in the user's element whose name is * configured by the <code>userRoleName</code> property.</li> * <li>A default role can be assigned to each user that was successfully authenticated by setting the * <code>commonRole</code> property to the name of this role. The role doesn't have to exist in the directory.</li> * <li>If the directory server contains nested roles, you can search for them by setting <code>roleNested</code> to * <code>true</code>. The default value is <code>false</code>, so role searches will not find nested roles.</li> * <li>Note that the standard <code><security-role-ref></code> element in the web application deployment * descriptor allows applications to refer to roles programmatically by names other than those used in the directory * server itself.</li> * </ul> * <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 * report is here: https://bz.apache.org/bugzilla/show_bug.cgi?id=11210 . With luck, Netscape has updated their provider * code and this is not an issue. * </p> * * @author John Holman * @author Craig R. McClanahan
*/ publicclass JNDIRealm extends RealmBase {
/** * The type of authentication to use
*/ protected String authentication = null;
/** * The connection username for the server we will contact.
*/ protected String connectionName = null;
/** * The connection password for the server we will contact.
*/ protected String connectionPassword = null;
/** * The connection URL for the server we will contact.
*/ protected String connectionURL = null;
/** * The JNDI context factory used to acquire our InitialContext. By default, assumes use of an LDAP server using the * standard JNDI LDAP provider.
*/ protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
/** * How aliases should be dereferenced during search operations.
*/ protected String derefAliases = null;
/** * Constant that holds the name of the environment property for specifying the manner in which aliases should be * dereferenced.
*/ publicstaticfinal String DEREF_ALIASES = "java.naming.ldap.derefAliases";
/** * The protocol that will be used in the communication with the directory server.
*/ protected String protocol = null;
/** * Should we ignore PartialResultExceptions when iterating over NamingEnumerations? Microsoft Active Directory often * returns referrals, which lead to PartialResultExceptions. Unfortunately there's no stable way to detect, if the * Exceptions really come from an AD referral. Set to true to ignore PartialResultExceptions.
*/ protectedboolean adCompat = false;
/** * How should we handle referrals? Microsoft Active Directory often returns referrals. If you need to follow them * set referrals to "follow". Caution: if your DNS is not part of AD, the LDAP client lib might try to resolve your * domain name in DNS to find another LDAP server.
*/ protected String referrals = null;
/** * The base element for user searches.
*/ protected String userBase = "";
/** * The message format used to search for a user, with "{0}" marking the spot where the username goes.
*/ protected String userSearch = null;
/** * When searching for users, should the search be performed as the user currently being authenticated? If false, * {@link #connectionName} and {@link #connectionPassword} will be used if specified, else an anonymous connection * will be used.
*/ privateboolean userSearchAsUser = false;
/** * Should we search the entire subtree for matching users?
*/ protectedboolean userSubtree = false;
/** * The attribute name used to retrieve the user password.
*/ protected String userPassword = null;
/** * The name of the attribute inside the users directory entry where the value will be taken to search for roles This * attribute is not used during a nested search
*/ protected String userRoleAttribute = null;
/** * A string of LDAP user patterns or paths, ":"-separated These will be used to form the distinguished name of a * user, with "{0}" marking the spot where the specified username goes. This is similar to userPattern, but allows * for multiple searches for a user.
*/ protected String[] userPatternArray = null;
/** * The message format used to form the distinguished name of a user, with "{0}" marking the spot where the specified * username goes.
*/ protected String userPattern = null;
/** * The base element for role searches.
*/ protected String roleBase = "";
/** * The name of an attribute in the user's entry containing roles for that user
*/ protected String userRoleName = null;
/** * The name of the attribute containing roles held elsewhere
*/ protected String roleName = null;
/** * The message format used to select roles for a user, with "{0}" marking the spot where the distinguished name of * the user goes. The "{1}" and "{2}" are described in the Configuration Reference.
*/ protected String roleSearch = null;
/** * Should we search the entire subtree for matching memberships?
*/ protectedboolean roleSubtree = false;
/** * Should we look for nested group in order to determine roles?
*/ protectedboolean roleNested = false;
/** * When searching for user roles, should the search be performed as the user currently being authenticated? If * false, {@link #connectionName} and {@link #connectionPassword} will be used if specified, else an anonymous * connection will be used.
*/ protectedboolean roleSearchAsUser = false;
/** * An alternate URL, to which, we should connect if connectionURL fails.
*/ protected String alternateURL;
/** * The number of connection attempts. If greater than zero we use the alternate url.
*/ protectedint connectionAttempt = 0;
/** * Add this role to every authenticated user
*/ protected String commonRole = null;
/** * The timeout, in milliseconds, to use when trying to create a connection to the directory. The default is 5000 (5 * seconds).
*/ protected String connectionTimeout = "5000";
/** * The timeout, in milliseconds, to use when trying to read from a connection to the directory. The default is 5000 * (5 seconds).
*/ protected String readTimeout = "5000";
/** * The sizeLimit (also known as the countLimit) to use when the realm is configured with {@link #userSearch}. Zero * for no limit.
*/ protectedlong sizeLimit = 0;
/** * The timeLimit (in milliseconds) to use when the realm is configured with {@link #userSearch}. Zero for no limit.
*/ protectedint timeLimit = 0;
/** * Should delegated credentials from the SPNEGO authenticator be used if available
*/ protectedboolean useDelegatedCredential = true;
/** * The QOP that should be used for the connection to the LDAP server after authentication. This value is used to set * the <code>javax.security.sasl.qop</code> environment property for the LDAP connection.
*/ protected String spnegoDelegationQop = "auth-conf";
/** * Whether to use TLS for connections
*/ privateboolean useStartTls = false;
private StartTlsResponse tls = null;
/** * The list of enabled cipher suites used for establishing tls connections. <code>null</code> means to use the * default cipher suites.
*/ private String[] cipherSuitesArray = null;
/** * Verifier for hostnames in a StartTLS secured connection. <code>null</code> means to use the default verifier.
*/ private HostnameVerifier hostnameVerifier = null;
/** * {@link SSLSocketFactory} to use when connection with StartTLS enabled.
*/ private SSLSocketFactory sslSocketFactory = null;
/** * Name of the class of the {@link SSLSocketFactory}. <code>null</code> means to use the default factory.
*/ private String sslSocketFactoryClassName;
/** * Comma separated list of cipher suites to use for StartTLS. If empty, the default suites are used.
*/ private String cipherSuites;
/** * Name of the class of the {@link HostnameVerifier}. <code>null</code> means to use the default verifier.
*/ private String hostNameVerifierClassName;
/** * The ssl Protocol which will be used by StartTLS.
*/ private String sslProtocol;
privateboolean forceDnHexEscape = false;
/** * Non pooled connection to our directory server.
*/ protected JNDIConnection singleConnection;
/** * The lock to ensure single connection thread safety.
*/ protectedfinal Lock singleConnectionLock = new ReentrantLock();
/** * Connection pool.
*/ protected SynchronizedStack<JNDIConnection> connectionPool = null;
/** * The pool size limit. If 1, pooling is not used.
*/ protectedint connectionPoolSize = 1;
/** * Whether to use context ClassLoader or default ClassLoader. True means use context ClassLoader, and True is the * default value.
*/ protectedboolean useContextClassLoader = true;
/** * @return the type of authentication to use.
*/ public String getAuthentication() { return authentication;
}
/** * Set the type of authentication to use. * * @param authentication The authentication
*/ publicvoid setAuthentication(String authentication) { this.authentication = authentication;
}
/** * @return the connection username for this Realm.
*/ public String getConnectionName() { returnthis.connectionName;
}
/** * Set the connection username for this Realm. * * @param connectionName The new connection username
*/ publicvoid setConnectionName(String connectionName) { this.connectionName = connectionName;
}
/** * @return the connection password for this Realm.
*/ public String getConnectionPassword() { returnthis.connectionPassword;
}
/** * Set the connection password for this Realm. * * @param connectionPassword The new connection password
*/ publicvoid setConnectionPassword(String connectionPassword) { this.connectionPassword = connectionPassword;
}
/** * @return the connection URL for this Realm.
*/ public String getConnectionURL() { returnthis.connectionURL;
}
/** * Set the connection URL for this Realm. * * @param connectionURL The new connection URL
*/ publicvoid setConnectionURL(String connectionURL) { this.connectionURL = connectionURL;
}
/** * @return the JNDI context factory for this Realm.
*/ public String getContextFactory() { returnthis.contextFactory;
}
/** * Set the JNDI context factory for this Realm. * * @param contextFactory The new context factory
*/ publicvoid setContextFactory(String contextFactory) { this.contextFactory = contextFactory;
}
/** * @return the derefAliases setting to be used.
*/ public String getDerefAliases() { return derefAliases;
}
/** * Set the value for derefAliases to be used when searching the directory. * * @param derefAliases New value of property derefAliases.
*/ publicvoid setDerefAliases(String derefAliases) { this.derefAliases = derefAliases;
}
/** * @return the protocol to be used.
*/ public String getProtocol() { return protocol;
}
/** * Set the protocol for this Realm. * * @param protocol The new protocol.
*/ publicvoid setProtocol(String protocol) { this.protocol = protocol;
}
/** * @return the current settings for handling PartialResultExceptions
*/ publicboolean getAdCompat() { return adCompat;
}
/** * How do we handle PartialResultExceptions? True: ignore all PartialResultExceptions. * * @param adCompat <code>true</code> to ignore partial results
*/ publicvoid setAdCompat(boolean adCompat) { this.adCompat = adCompat;
}
/** * @return the current settings for handling JNDI referrals.
*/ public String getReferrals() { return referrals;
}
/** * How do we handle JNDI referrals? ignore, follow, or throw (see javax.naming.Context.REFERRAL for more * information). * * @param referrals The referral handling
*/ publicvoid setReferrals(String referrals) { this.referrals = referrals;
}
/** * @return the base element for user searches.
*/ public String getUserBase() { returnthis.userBase;
}
/** * Set the base element for user searches. * * @param userBase The new base element
*/ publicvoid setUserBase(String userBase) { this.userBase = userBase;
}
/** * @return the message format pattern for selecting users in this Realm.
*/ public String getUserSearch() { returnthis.userSearch;
}
/** * Set the message format pattern for selecting users in this Realm. * * @param userSearch The new user search pattern
*/ publicvoid setUserSearch(String userSearch) { this.userSearch = userSearch;
singleConnection = create();
}
/** * @return the "search subtree for users" flag.
*/ publicboolean getUserSubtree() { returnthis.userSubtree;
}
/** * Set the "search subtree for users" flag. * * @param userSubtree The new search flag
*/ publicvoid setUserSubtree(boolean userSubtree) { this.userSubtree = userSubtree;
}
/** * @return the user role name attribute name for this Realm.
*/ public String getUserRoleName() { return userRoleName;
}
/** * Set the user role name attribute name for this Realm. * * @param userRoleName The new userRole name attribute name
*/ publicvoid setUserRoleName(String userRoleName) { this.userRoleName = userRoleName;
}
/** * @return the base element for role searches.
*/ public String getRoleBase() { returnthis.roleBase;
}
/** * Set the base element for role searches. * * @param roleBase The new base element
*/ publicvoid setRoleBase(String roleBase) { this.roleBase = roleBase;
singleConnection = create();
}
/** * @return the role name attribute name for this Realm.
*/ public String getRoleName() { returnthis.roleName;
}
/** * Set the role name attribute name for this Realm. * * @param roleName The new role name attribute name
*/ publicvoid setRoleName(String roleName) { this.roleName = roleName;
}
/** * @return the message format pattern for selecting roles in this Realm.
*/ public String getRoleSearch() { returnthis.roleSearch;
}
/** * Set the message format pattern for selecting roles in this Realm. * * @param roleSearch The new role search pattern
*/ publicvoid setRoleSearch(String roleSearch) { this.roleSearch = roleSearch;
singleConnection = create();
}
/** * @return the "search subtree for roles" flag.
*/ publicboolean getRoleSubtree() { returnthis.roleSubtree;
}
/** * Set the "search subtree for roles" flag. * * @param roleSubtree The new search flag
*/ publicvoid setRoleSubtree(boolean roleSubtree) { this.roleSubtree = roleSubtree;
}
/** * @return the "The nested group search flag" flag.
*/ publicboolean getRoleNested() { returnthis.roleNested;
}
/** * Set the "search subtree for roles" flag. * * @param roleNested The nested group search flag
*/ publicvoid setRoleNested(boolean roleNested) { this.roleNested = roleNested;
}
/** * @return the password attribute used to retrieve the user password.
*/ public String getUserPassword() { returnthis.userPassword;
}
/** * Set the password attribute used to retrieve the user password. * * @param userPassword The new password attribute
*/ publicvoid setUserPassword(String userPassword) { this.userPassword = userPassword;
}
public String getUserRoleAttribute() { return userRoleAttribute;
}
/** * @return the message format pattern for selecting users in this Realm.
*/ public String getUserPattern() { returnthis.userPattern;
}
/** * Set the message format pattern for selecting users in this Realm. This may be one simple pattern, or multiple * patterns to be tried, separated by parentheses. (for example, either "cn={0}", or "(cn={0})(cn={0},o=myorg)" Full * LDAP search strings are also supported, but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is also * valid. Complex search strings with &, etc are NOT supported. * * @param userPattern The new user pattern
*/ publicvoid setUserPattern(String userPattern) { this.userPattern = userPattern; if (userPattern == null) {
userPatternArray = null;
} else {
userPatternArray = parseUserPatternString(userPattern);
singleConnection = create();
}
}
/** * Getter for property alternateURL. * * @return Value of property alternateURL.
*/ public String getAlternateURL() { returnthis.alternateURL;
}
/** * Setter for property alternateURL. * * @param alternateURL New value of property alternateURL.
*/ publicvoid setAlternateURL(String alternateURL) { this.alternateURL = alternateURL;
}
/** * @return the common role
*/ public String getCommonRole() { return commonRole;
}
/** * Set the common role * * @param commonRole The common role
*/ publicvoid setCommonRole(String commonRole) { this.commonRole = commonRole;
}
/** * @return the connection timeout.
*/ public String getConnectionTimeout() { return connectionTimeout;
}
/** * Set the connection timeout. * * @param timeout The new connection timeout
*/ publicvoid setConnectionTimeout(String timeout) { this.connectionTimeout = timeout;
}
/** * @return the read timeout.
*/ public String getReadTimeout() { return readTimeout;
}
/** * Set the read timeout. * * @param timeout The new read timeout
*/ publicvoid setReadTimeout(String timeout) { this.readTimeout = timeout;
}
/** * @return flag whether to use StartTLS for connections to the ldap server
*/ publicboolean getUseStartTls() { return useStartTls;
}
/** * Flag whether StartTLS should be used when connecting to the ldap server * * @param useStartTls {@code true} when StartTLS should be used. Default is {@code false}.
*/ publicvoid setUseStartTls(boolean useStartTls) { this.useStartTls = useStartTls;
}
/** * @return list of the allowed cipher suites when connections are made using StartTLS
*/ private String[] getCipherSuitesArray() { if (cipherSuites == null || cipherSuitesArray != null) { return cipherSuitesArray;
} if (this.cipherSuites.trim().isEmpty()) {
containerLog.warn(sm.getString("jndiRealm.emptyCipherSuites")); this.cipherSuitesArray = null;
} else { this.cipherSuitesArray = cipherSuites.trim().split("\\s*,\\s*");
containerLog.debug(sm.getString("jndiRealm.cipherSuites", Arrays.toString(this.cipherSuitesArray)));
} returnthis.cipherSuitesArray;
}
/** * Set the allowed cipher suites when opening a connection using StartTLS. The cipher suites are expected as a comma * separated list. * * @param suites comma separated list of allowed cipher suites
*/ publicvoid setCipherSuites(String suites) { this.cipherSuites = suites;
}
/** * @return the connection pool size, or the default value 1 if pooling is disabled
*/ publicint getConnectionPoolSize() { return connectionPoolSize;
}
/** * Set the connection pool size * * @param connectionPoolSize the new pool size
*/ publicvoid 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 should be used.
*/ public String getHostnameVerifierClassName() { if (this.hostnameVerifier == null) { return"";
} returnthis.hostnameVerifier.getClass().getCanonicalName();
}
/** * Set the {@link HostnameVerifier} to be used when opening connections using StartTLS. An instance of the given * class name will be constructed using the default constructor. * * @param verifierClassName class name of the {@link HostnameVerifier} to be constructed
*/ publicvoid setHostnameVerifierClassName(String verifierClassName) { if (verifierClassName != null) { this.hostNameVerifierClassName = verifierClassName.trim();
} else { this.hostNameVerifierClassName = null;
}
}
/** * @return the {@link HostnameVerifier} to use for peer certificate verification when opening connections using * StartTLS.
*/ public HostnameVerifier getHostnameVerifier() { if (this.hostnameVerifier != null) { returnthis.hostnameVerifier;
} if (this.hostNameVerifierClassName == null || hostNameVerifierClassName.equals("")) { returnnull;
} try {
Object o = constructInstance(hostNameVerifierClassName); if (o instanceof HostnameVerifier) { this.hostnameVerifier = (HostnameVerifier) o; returnthis.hostnameVerifier;
} else { thrownew IllegalArgumentException(
sm.getString("jndiRealm.invalidHostnameVerifier", hostNameVerifierClassName));
}
} catch (ReflectiveOperationException | SecurityException e) { thrownew IllegalArgumentException(
sm.getString("jndiRealm.invalidHostnameVerifier", hostNameVerifierClassName), e);
}
}
/** * Set the {@link SSLSocketFactory} to be used when opening connections using StartTLS. An instance of the factory * with the given name will be created using the default constructor. The SSLSocketFactory can also be set using * {@link JNDIRealm#setSslProtocol(String) setSslProtocol(String)}. * * @param factoryClassName class name of the factory to be constructed
*/ publicvoid setSslSocketFactoryClassName(String factoryClassName) { this.sslSocketFactoryClassName = factoryClassName;
}
/** * Set the ssl protocol to be used for connections using StartTLS. * * @param protocol one of the allowed ssl protocol names
*/ publicvoid setSslProtocol(String protocol) { this.sslProtocol = protocol;
}
/** * @return the list of supported ssl protocols by the default {@link SSLContext}
*/ private String[] getSupportedSslProtocols() { try {
SSLContext sslContext = SSLContext.getDefault(); return sslContext.getSupportedSSLParameters().getProtocols();
} catch (NoSuchAlgorithmException e) { thrownew RuntimeException(sm.getString("jndiRealm.exception"), e);
}
}
/** * Sets whether to use the context or default ClassLoader. True means use context ClassLoader. * * @param useContext True means use context ClassLoader
*/ publicvoid setUseContextClassLoader(boolean useContext) {
useContextClassLoader = useContext;
}
/** * Returns whether to use the context or default ClassLoader. True means to use the context ClassLoader. * * @return The value of useContextClassLoader
*/ publicboolean isUseContextClassLoader() { return useContextClassLoader;
}
/** * {@inheritDoc} * <p> * If there are any errors with the JNDI connection, executing the query or anything we return null (don't * authenticate). This event is also logged, and the connection will be closed so that a subsequent request will * automatically re-open it.
*/
@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);
} catch (NullPointerException | NamingException e) { /* * BZ 61313 NamingException may or may not indicate an error that is recoverable via fail over. * Therefore a decision needs to be made whether to fail over or not. Generally, attempting to fail over * when it is not appropriate is better than not failing over when it is appropriate so the code always * attempts to fail over for NamingExceptions.
*/
// 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.
principal = authenticate(connection, username, credentials);
}
// Release this context
release(connection);
// Return the authenticated Principal (if any) return principal;
} 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();
// Return "not authenticated" for this request if (containerLog.isDebugEnabled()) {
containerLog.debug("Returning null principal.");
} returnnull;
} finally { if (currentThread != null) {
currentThread.setContextClassLoader(ocl);
}
}
}
/** * Return the Principal associated with the specified username and credentials, if there is one; otherwise return * <code>null</code>. * * @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 * * @return the associated principal, or <code>null</code> if there is none. * * @exception NamingException if a directory server error occurs
*/ public Principal authenticate(JNDIConnection connection, String username, String credentials) throws NamingException {
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 * 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
*/
@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 * 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
*/
@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 * 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
*/
@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 * 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
*/
@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 * 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
*/
@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);
}
}
}
/** * Return a User object containing information about the user with the specified username, if found in the * directory; otherwise return <code>null</code>. * * @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(JNDIConnection, String, String, int)
*/ protected User getUser(JNDIConnection connection, String username) throws NamingException { return getUser(connection, username, null, -1);
}
/** * Return a User object containing information about the user with the specified username, if found in the * directory; otherwise return <code>null</code>. * * @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(JNDIConnection, String, String, int)
*/ protected User getUser(JNDIConnection connection, String username, String credentials) throws NamingException { return getUser(connection, username, credentials, -1);
}
/** * Return a User object containing information about the user with the specified username, if found in the * directory; otherwise return <code>null</code>. If the <code>userPassword</code> configuration attribute is * specified, the value of that attribute is retrieved from the user's directory entry. If the * <code>userRoleName</code> configuration attribute is specified, all values of that attribute are retrieved from * the directory entry. * * @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(JNDIConnection connection, String username, String credentials, int curUserPattern) throws NamingException {
User user = null;
// 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());
}
return user;
}
/** * Use the distinguished name to locate the directory entry for the user with the specified username and return a * User object; otherwise return <code>null</code>. * * @param context The directory context * @param username The username * @param attrIds String[]containing names of attributes to * @param dn Distinguished name of the user retrieve. * * @return the User object * * @exception NamingException if a directory server error occurs
*/ protected User getUserByPattern(DirContext context, String username, String[] attrIds, String dn) throws NamingException {
// 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);
}
/** * Use the <code>UserPattern</code> configuration attribute to locate the directory entry for the user with the * specified username and return a User object; otherwise return <code>null</code>. * * @param connection The directory context * @param username The username * @param credentials User credentials (optional) * @param attrIds String[]containing names of attributes to * @param curUserPattern Index into userPatternFormatArray * * @return the User object * * @exception NamingException if a directory server error occurs * * @see #getUserByPattern(DirContext, String, String[], String)
*/ protected User getUserByPattern(JNDIConnection connection, String username, String credentials, String[] attrIds, int curUserPattern) throws NamingException {
// 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);
/** * Search the directory to return a User object containing information about the user with the specified username, * if found in the directory; otherwise return <code>null</code>. * * @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(JNDIConnection connection, String username, String[] attrIds) throws NamingException {
// 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;
}
}
/** * Check whether the given User can be authenticated with the given credentials. If the <code>userPassword</code> * configuration attribute is specified, the credentials previously retrieved from the directory are compared * explicitly with those presented by the user. Otherwise the presented credentials are checked by binding to the * directory as the user. * * @param context The directory context * @param user The User to be authenticated * @param credentials The credentials presented by the user * * @return <code>true</code> if the credentials are validated * * @exception NamingException if a directory server error occurs
*/ protectedboolean checkCredentials(DirContext context, User user, String credentials) throws NamingException {
if (containerLog.isTraceEnabled()) { if (validated) {
containerLog.trace(sm.getString("jndiRealm.authenticateSuccess", user.getUserName()));
} else {
containerLog.trace(sm.getString("jndiRealm.authenticateFailure", user.getUserName()));
}
} return validated;
}
/** * Check whether the credentials presented by the user match those retrieved from the directory. * * @param context The directory context * @param info The User to be authenticated * @param credentials Authentication credentials * * @return <code>true</code> if the credentials are validated * * @exception NamingException if a directory server error occurs
*/ protectedboolean compareCredentials(DirContext context, User info, String credentials) throws NamingException { // Validate the credentials specified by the user if (containerLog.isTraceEnabled()) {
containerLog.trace(" validating credentials");
}
/** * Check credentials by binding to the directory as the user * * @param context The directory context * @param user The User to be authenticated * @param credentials Authentication credentials * * @return <code>true</code> if the credentials are validated * * @exception NamingException if a directory server error occurs
*/ protectedboolean bindAsUser(DirContext context, User user, String credentials) throws NamingException {
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");
}
/** * Configure the context to use the provided credentials for authentication. * * @param context DirContext to configure * @param dn Distinguished name of user * @param credentials Credentials of user * * @exception NamingException if a directory server error occurs
*/ privatevoid userCredentialsAdd(DirContext context, String dn, String credentials) throws NamingException { // Set up security environment to bind as the user
context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
}
/** * Configure the context to use {@link #connectionName} and {@link #connectionPassword} if specified or an anonymous * connection if those attributes are not specified. * * @param context DirContext to configure * * @exception NamingException if a directory server error occurs
*/ privatevoid userCredentialsRemove(DirContext context) throws NamingException { // Restore the original security environment if (connectionName != null) {
context.addToEnvironment(Context.SECURITY_PRINCIPAL, connectionName);
} else {
context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
}
/** * Return a List of roles associated with the given User. Any roles present in the user's directory entry are * supplemented by a directory search. If no roles are associated with this user, a zero-length List is returned. * * @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(JNDIConnection connection, User user) throws NamingException {
if (user == null) { returnnull;
}
// 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");
--> --------------------
--> maximum size reached
--> --------------------
¤ 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.0.79Bemerkung:
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
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 ist noch experimentell.