|
Java example source code file (LdapLoginModule.java)
The LdapLoginModule.java Java example source code/* * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.security.auth.module; import java.security.AccessController; import java.net.SocketPermission; import java.security.Principal; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Hashtable; import java.util.Map; import java.util.ResourceBundle; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.Set; import javax.naming.*; import javax.naming.directory.*; import javax.naming.ldap.*; import javax.security.auth.*; import javax.security.auth.callback.*; import javax.security.auth.login.*; import javax.security.auth.spi.*; import com.sun.security.auth.LdapPrincipal; import com.sun.security.auth.UserPrincipal; /** * This {@link LoginModule} performs LDAP-based authentication. * A username and password is verified against the corresponding user * credentials stored in an LDAP directory. * This module requires the supplied {@link CallbackHandler} to support a * {@link NameCallback} and a {@link PasswordCallback}. * If authentication is successful then a new {@link LdapPrincipal} is created * using the user's distinguished name and a new {@link UserPrincipal} is * created using the user's username and both are associated * with the current {@link Subject}. * * <p> This module operates in one of three modes: search-first, * <i>authentication-first or authentication-only. * A mode is selected by specifying a particular set of options. * * <p> In search-first mode, the LDAP directory is searched to determine the * user's distinguished name and then authentication is attempted. * An (anonymous) search is performed using the supplied username in * conjunction with a specified search filter. * If successful then authentication is attempted using the user's * distinguished name and the supplied password. * To enable this mode, set the <code>userFilter option and omit the * <code>authIdentity option. * Use search-first mode when the user's distinguished name is not * known in advance. * * <p> In authentication-first mode, authentication is attempted using the * supplied username and password and then the LDAP directory is searched. * If authentication is successful then a search is performed using the * supplied username in conjunction with a specified search filter. * To enable this mode, set the <code>authIdentity and the * <code>userFilter options. * Use authentication-first mode when accessing an LDAP directory * that has been configured to disallow anonymous searches. * * <p> In authentication-only mode, authentication is attempted using the * supplied username and password. The LDAP directory is not searched because * the user's distinguished name is already known. * To enable this mode, set the <code>authIdentity option to a valid * distinguished name and omit the <code>userFilter option. * Use authentication-only mode when the user's distinguished name is * known in advance. * * <p> The following option is mandatory and must be specified in this * module's login {@link Configuration}: * <dl> userProvider=ldap_urls
* </dt>
* <dd> This option identifies the LDAP directory that stores user entries.
* <b>ldap_urls is a list of space-separated LDAP URLs
* (<a href="http://www.ietf.org/rfc/rfc2255.txt">RFC 2255)
* that identifies the LDAP server to use and the position in
* its directory tree where user entries are located.
* When several LDAP URLs are specified then each is attempted,
* in turn, until the first successful connection is established.
* Spaces in the distinguished name component of the URL must be escaped
* using the standard mechanism of percent character ('<code>%')
* followed by two hexadecimal digits (see {@link java.net.URI}).
* Query components must also be omitted from the URL.
*
* <p>
* Automatic discovery of the LDAP server via DNS
* (<a href="http://www.ietf.org/rfc/rfc2782.txt">RFC 2782)
* is supported (once DNS has been configured to support such a service).
* It is enabled by omitting the hostname and port number components from
* the LDAP URL. </dd>
* </dl>
*
* <p> This module also recognizes the following optional {@link Configuration}
* options:
* <dl>userFilter=ldap_filter
* <dd> This option specifies the search filter to use to locate a user's
* entry in the LDAP directory. It is used to determine a user's
* distinguished name.
* <code>ldap_filter is an LDAP filter string
* (<a href="http://www.ietf.org/rfc/rfc2254.txt">RFC 2254).
* If it contains the special token "<code>{USERNAME}"
* then that token will be replaced with the supplied username value
* before the filter is used to search the directory. </dd>
*
* <dt> authIdentity=auth_id
* <dd> This option specifies the identity to use when authenticating a user
* to the LDAP directory.
* <code>auth_id may be an LDAP distinguished name string
* (<a href="http://www.ietf.org/rfc/rfc2253.txt">RFC 2253) or some
* other string name.
* It must contain the special token "<code>{USERNAME}"
* which will be replaced with the supplied username value before the
* name is used for authentication.
* Note that if this option does not contain a distinguished name then
* the <code>userFilter option must also be specified. authzIdentity=authz_id
* <dd> This option specifies an authorization identity for the user.
* <code>authz_id is any string name.
* If it comprises a single special token with curly braces then
* that token is treated as a attribute name and will be replaced with a
* single value of that attribute from the user's LDAP entry.
* If the attribute cannot be found then the option is ignored.
* When this option is supplied and the user has been successfully
* authenticated then an additional {@link UserPrincipal}
* is created using the authorization identity and it is associated with
* the current {@link Subject}. </dd>
*
* <dt> useSSL
* <dd> if false , this module does not establish an SSL connection
* to the LDAP server before attempting authentication. SSL is used to
* protect the privacy of the user's password because it is transmitted
* in the clear over LDAP.
* By default, this module uses SSL. </dd>
*
* <dt> useFirstPass
* <dd> if true , this module retrieves the username and password
* from the module's shared state, using "javax.security.auth.login.name"
* and "javax.security.auth.login.password" as the respective keys. The
* retrieved values are used for authentication. If authentication fails,
* no attempt for a retry is made, and the failure is reported back to
* the calling application.</dd>
*
* <dt> tryFirstPass
* <dd> if true , this module retrieves the username and password
* from the module's shared state, using "javax.security.auth.login.name"
* and "javax.security.auth.login.password" as the respective keys. The
* retrieved values are used for authentication. If authentication fails,
* the module uses the {@link CallbackHandler} to retrieve a new username
* and password, and another attempt to authenticate is made. If the
* authentication fails, the failure is reported back to the calling
* application.</dd>
*
* <dt> storePass
* <dd> if true , this module stores the username and password
* obtained from the {@link CallbackHandler} in the module's shared state,
* using
* "javax.security.auth.login.name" and
* "javax.security.auth.login.password" as the respective keys. This is
* not performed if existing values already exist for the username and
* password in the shared state, or if authentication fails.</dd>
*
* <dt> clearPass
* <dd> if true , this module clears the username and password
* stored in the module's shared state after both phases of authentication
* (login and commit) have completed.</dd>
*
* <dt> debug
* <dd> if true , debug messages are displayed on the standard
* output stream.
* </dl>
* </dl>
*
* <p>
* Arbitrary
* <a href="{@docRoot}/../../../../../technotes/guides/jndi/jndi-ldap-gl.html#PROP">JNDI properties
* may also be specified in the {@link Configuration}.
* They are added to the environment and passed to the LDAP provider.
* Note that the following four JNDI properties are set by this module directly
* and are ignored if also present in the configuration:
* <ul>
* <li> java.naming.provider.url
* <li> java.naming.security.principal
* <li> java.naming.security.credentials
* <li> java.naming.security.protocol
* </ul>
*
* <p>
* Three sample {@link Configuration}s are shown below.
* The first one activates search-first mode. It identifies the LDAP server
* and specifies that users' entries be located by their <code>uid and
* <code>objectClass attributes. It also specifies that an identity
* based on the user's <code>employeeNumber attribute should be created.
* The second one activates authentication-first mode. It requests that the
* LDAP server be located dynamically, that authentication be performed using
* the supplied username directly but without the protection of SSL and that
* users' entries be located by one of three naming attributes and their
* <code>objectClass attribute.
* The third one activates authentication-only mode. It identifies alternative
* LDAP servers, it specifies the distinguished name to use for
* authentication and a fixed identity to use for authorization. No directory
* search is performed.
*
* <pre>
*
* ExampleApplication {
* com.sun.security.auth.module.LdapLoginModule REQUIRED
* userProvider="ldap://ldap-svr/ou=people,dc=example,dc=com"
* userFilter="(&(uid={USERNAME})(objectClass=inetOrgPerson))"
* authzIdentity="{EMPLOYEENUMBER}"
* debug=true;
* };
*
* ExampleApplication {
* com.sun.security.auth.module.LdapLoginModule REQUIRED
* userProvider="ldap:///cn=users,dc=example,dc=com"
* authIdentity="{USERNAME}"
* userFilter="(&(|(samAccountName={USERNAME})(userPrincipalName={USERNAME})(cn={USERNAME}))(objectClass=user))"
* useSSL=false
* debug=true;
* };
*
* ExampleApplication {
* com.sun.security.auth.module.LdapLoginModule REQUIRED
* userProvider="ldap://ldap-svr1 ldap://ldap-svr2"
* authIdentity="cn={USERNAME},ou=people,dc=example,dc=com"
* authzIdentity="staff"
* debug=true;
* };
*
* </pre>
*
* <dl>
* <dt>Note:
* <dd>When a {@link SecurityManager} is active then an application
* that creates a {@link LoginContext} and uses a {@link LoginModule}
* must be granted certain permissions.
* <p>
* If the application creates a login context using an <em>installed
* {@link Configuration} then the application must be granted the
* {@link AuthPermission} to create login contexts.
* For example, the following security policy allows an application in
* the user's current directory to instantiate <em>any login context:
* <pre>
*
* grant codebase "file:${user.dir}/" {
* permission javax.security.auth.AuthPermission "createLoginContext.*";
* };
* </pre>
*
* Alternatively, if the application creates a login context using a
* <em>caller-specified {@link Configuration} then the application
* must be granted the permissions required by the {@link LoginModule}.
* <em>This module requires the following two permissions:
* <p>
* <ul>
* <li> The {@link SocketPermission} to connect to an LDAP server.
* <li> The {@link AuthPermission} to modify the set of {@link Principal}s
* associated with a {@link Subject}.
* </ul>
* <p>
* For example, the following security policy grants an application in the
* user's current directory all the permissions required by this module:
* <pre>
*
* grant codebase "file:${user.dir}/" {
* permission java.net.SocketPermission "*:389", "connect";
* permission java.net.SocketPermission "*:636", "connect";
* permission javax.security.auth.AuthPermission "modifyPrincipals";
* };
* </pre>
* </dd>
* </dl>
*
* @since 1.6
*/
@jdk.Exported
public class LdapLoginModule implements LoginModule {
// Use the default classloader for this class to load the prompt strings.
private static final ResourceBundle rb = AccessController.doPrivileged(
new PrivilegedAction<ResourceBundle>() {
public ResourceBundle run() {
return ResourceBundle.getBundle(
"sun.security.util.AuthResources");
}
}
);
// Keys to retrieve the stored username and password
private static final String USERNAME_KEY = "javax.security.auth.login.name";
private static final String PASSWORD_KEY =
"javax.security.auth.login.password";
// Option names
private static final String USER_PROVIDER = "userProvider";
private static final String USER_FILTER = "userFilter";
private static final String AUTHC_IDENTITY = "authIdentity";
private static final String AUTHZ_IDENTITY = "authzIdentity";
// Used for the username token replacement
private static final String USERNAME_TOKEN = "{USERNAME}";
private static final Pattern USERNAME_PATTERN =
Pattern.compile("\\{USERNAME\\}");
// Configurable options
private String userProvider;
private String userFilter;
private String authcIdentity;
private String authzIdentity;
private String authzIdentityAttr = null;
private boolean useSSL = true;
private boolean authFirst = false;
private boolean authOnly = false;
private boolean useFirstPass = false;
private boolean tryFirstPass = false;
private boolean storePass = false;
private boolean clearPass = false;
private boolean debug = false;
// Authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
// Supplied username and password
private String username;
private char[] password;
// User's identities
private LdapPrincipal ldapPrincipal;
private UserPrincipal userPrincipal;
private UserPrincipal authzPrincipal;
// Initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String, Object> sharedState;
private Map<String, ?> options;
private LdapContext ctx;
private Matcher identityMatcher = null;
private Matcher filterMatcher = null;
private Hashtable<String, Object> ldapEnvironment;
private SearchControls constraints = null;
/**
* Initialize this <code>LoginModule.
*
* @param subject the <code>Subject to be authenticated.
* @param callbackHandler a <code>CallbackHandler to acquire the
* username and password.
* @param sharedState shared <code>LoginModule state.
* @param options options specified in the login
* <code>Configuration for this particular
* <code>LoginModule.
*/
// Unchecked warning from (Map<String, Object>)sharedState is safe
// since javax.security.auth.login.LoginContext passes a raw HashMap.
@SuppressWarnings("unchecked")
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, MapUserPrincipal s
* with the <code>Subject located in the
* <code>LoginModule. If this LoginModule's own
* authentication attempted failed, then this method removes
* any state that was originally saved.
*
* @exception LoginException if the commit fails
* @return true if this LoginModule's own login and commit
* attempts succeeded, or false otherwise.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
if (subject.isReadOnly()) {
cleanState();
throw new LoginException ("Subject is read-only");
}
// add Principals to the Subject
Set<Principal> principals = subject.getPrincipals();
if (! principals.contains(ldapPrincipal)) {
principals.add(ldapPrincipal);
}
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"added LdapPrincipal \"" +
ldapPrincipal +
"\" to Subject");
}
if (! principals.contains(userPrincipal)) {
principals.add(userPrincipal);
}
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"added UserPrincipal \"" +
userPrincipal +
"\" to Subject");
}
if (authzPrincipal != null &&
(! principals.contains(authzPrincipal))) {
principals.add(authzPrincipal);
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"added UserPrincipal \"" +
authzPrincipal +
"\" to Subject");
}
}
}
// in any case, clean out state
cleanState();
commitSucceeded = true;
return true;
}
/**
* Abort user authentication.
*
* <p> This method is called if the overall authentication failed.
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* did not succeed).
*
* <p> If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login and commit methods),
* then this method cleans up any state that was originally saved.
*
* @exception LoginException if the abort fails.
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (debug)
System.out.println("\t\t[LdapLoginModule] " +
"aborted authentication");
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// Clean out state
succeeded = false;
cleanState();
ldapPrincipal = null;
userPrincipal = null;
authzPrincipal = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout a user.
*
* <p> This method removes the Principals
* that were added by the <code>commit method.
*
* @exception LoginException if the logout fails.
* @return true in all cases since this <code>LoginModule
* should not be ignored.
*/
public boolean logout() throws LoginException {
if (subject.isReadOnly()) {
cleanState();
throw new LoginException ("Subject is read-only");
}
Set<Principal> principals = subject.getPrincipals();
principals.remove(ldapPrincipal);
principals.remove(userPrincipal);
if (authzIdentity != null) {
principals.remove(authzPrincipal);
}
// clean out state
cleanState();
succeeded = false;
commitSucceeded = false;
ldapPrincipal = null;
userPrincipal = null;
authzPrincipal = null;
if (debug) {
System.out.println("\t\t[LdapLoginModule] logged out Subject");
}
return true;
}
/**
* Attempt authentication
*
* @param getPasswdFromSharedState boolean that tells this method whether
* to retrieve the password from the sharedState.
* @exception LoginException if the authentication attempt fails.
*/
private void attemptAuthentication(boolean getPasswdFromSharedState)
throws LoginException {
// first get the username and password
getUsernamePassword(getPasswdFromSharedState);
if (password == null || password.length == 0) {
throw (LoginException)
new FailedLoginException("No password was supplied");
}
String dn = "";
if (authFirst || authOnly) {
String id = replaceUsernameToken(identityMatcher, authcIdentity);
// Prepare to bind using user's username and password
ldapEnvironment.put(Context.SECURITY_CREDENTIALS, password);
ldapEnvironment.put(Context.SECURITY_PRINCIPAL, id);
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"attempting to authenticate user: " + username);
}
try {
// Connect to the LDAP server (using simple bind)
ctx = new InitialLdapContext(ldapEnvironment, null);
} catch (NamingException e) {
throw (LoginException)
new FailedLoginException("Cannot bind to LDAP server")
.initCause(e);
}
// Authentication has succeeded
// Locate the user's distinguished name
if (userFilter != null) {
dn = findUserDN(ctx);
} else {
dn = id;
}
} else {
try {
// Connect to the LDAP server (using anonymous bind)
ctx = new InitialLdapContext(ldapEnvironment, null);
} catch (NamingException e) {
throw (LoginException)
new FailedLoginException("Cannot connect to LDAP server")
.initCause(e);
}
// Locate the user's distinguished name
dn = findUserDN(ctx);
try {
// Prepare to bind using user's distinguished name and password
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"attempting to authenticate user: " + username);
}
// Connect to the LDAP server (using simple bind)
ctx.reconnect(null);
// Authentication has succeeded
} catch (NamingException e) {
throw (LoginException)
new FailedLoginException("Cannot bind to LDAP server")
.initCause(e);
}
}
// Save input as shared state only if authentication succeeded
if (storePass &&
!sharedState.containsKey(USERNAME_KEY) &&
!sharedState.containsKey(PASSWORD_KEY)) {
sharedState.put(USERNAME_KEY, username);
sharedState.put(PASSWORD_KEY, password);
}
// Create the user principals
userPrincipal = new UserPrincipal(username);
if (authzIdentity != null) {
authzPrincipal = new UserPrincipal(authzIdentity);
}
try {
ldapPrincipal = new LdapPrincipal(dn);
} catch (InvalidNameException e) {
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"cannot create LdapPrincipal: bad DN");
}
throw (LoginException)
new FailedLoginException("Cannot create LdapPrincipal")
.initCause(e);
}
}
/**
* Search for the user's entry.
* Determine the distinguished name of the user's entry and optionally
* an authorization identity for the user.
*
* @param ctx an LDAP context to use for the search
* @return the user's distinguished name or an empty string if none
* was found.
* @exception LoginException if the user's entry cannot be found.
*/
private String findUserDN(LdapContext ctx) throws LoginException {
String userDN = "";
// Locate the user's LDAP entry
if (userFilter != null) {
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"searching for entry belonging to user: " + username);
}
} else {
if (debug) {
System.out.println("\t\t[LdapLoginModule] " +
"cannot search for entry belonging to user: " + username);
}
throw (LoginException)
new FailedLoginException("Cannot find user's LDAP entry");
}
try {
NamingEnumeration<SearchResult> results = ctx.search("",
replaceUsernameToken(filterMatcher, userFilter), constraints);
// Extract the distinguished name of the user's entry
// (Use the first entry if more than one is returned)
if (results.hasMore()) {
SearchResult entry = results.next();
// %%% - use the SearchResult.getNameInNamespace method
// available in JDK 1.5 and later.
// (can remove call to constraints.setReturningObjFlag)
userDN = ((Context)entry.getObject()).getNameInNamespace();
if (debug) {
System.out.println("\t\t[LdapLoginModule] found entry: " +
userDN);
}
// Extract a value from user's authorization identity attribute
if (authzIdentityAttr != null) {
Attribute attr =
entry.getAttributes().get(authzIdentityAttr);
if (attr != null) {
Object val = attr.get();
if (val instanceof String) {
authzIdentity = (String) val;
}
}
}
results.close();
} else {
// Bad username
if (debug) {
System.out.println("\t\t[LdapLoginModule] user's entry " +
"not found");
}
}
} catch (NamingException e) {
// ignore
}
if (userDN.equals("")) {
throw (LoginException)
new FailedLoginException("Cannot find user's LDAP entry");
} else {
return userDN;
}
}
/**
* Replace the username token
*
* @param string the target string
* @return the modified string
*/
private String replaceUsernameToken(Matcher matcher, String string) {
return matcher != null ? matcher.replaceAll(username) : string;
}
/**
* Get the username and password.
* This method does not return any value.
* Instead, it sets global name and password variables.
*
* <p> Also note that this method will set the username and password
* values in the shared state in case subsequent LoginModules
* want to use them via use/tryFirstPass.
*
* @param getPasswdFromSharedState boolean that tells this method whether
* to retrieve the password from the sharedState.
* @exception LoginException if the username/password cannot be acquired.
*/
private void getUsernamePassword(boolean getPasswdFromSharedState)
throws LoginException {
if (getPasswdFromSharedState) {
// use the password saved by the first module in the stack
username = (String)sharedState.get(USERNAME_KEY);
password = (char[])sharedState.get(PASSWORD_KEY);
return;
}
// prompt for a username and password
if (callbackHandler == null)
throw new LoginException("No CallbackHandler available " +
"to acquire authentication information from the user");
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback(rb.getString("username."));
callbacks[1] = new PasswordCallback(rb.getString("password."), false);
try {
callbackHandler.handle(callbacks);
username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0,
password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to acquire authentication information" +
" from the user");
}
}
/**
* Clean out state because of a failed authentication attempt
*/
private void cleanState() {
username = null;
if (password != null) {
Arrays.fill(password, ' ');
password = null;
}
try {
if (ctx != null) {
ctx.close();
}
} catch (NamingException e) {
// ignore
}
ctx = null;
if (clearPass) {
sharedState.remove(USERNAME_KEY);
sharedState.remove(PASSWORD_KEY);
}
}
}
Other Java examples (source code examples)Here is a short list of links related to this Java LdapLoginModule.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.