The JavaTM Authentication and
Authorization Service (JAAS) was introduced as an optional package
to the JavaTM 2 SDK, Standard
Edition (J2SDK), v 1.3. JAAS was integrated into the Java
TM Standard Edition Development
Kit starting with J2SDK 1.4.
JAAS provides subject-based authorization on authenticated
identities. This document focuses on the authentication aspect of
JAAS, specifically the LoginModule
interface.
It also discusses various classes and interfaces in the JAAS
API. Please reference the javadocs for the JAAS API specification
for more detailed information:
Similar tutorials for JAAS authentication and authorization, but
which demonstrate the use of a Kerberos LoginModule and thus which
require a Kerberos installation, can be found at
These two tutorials are a part of the Java GSS-API and JAAS sequence of
tutorials that utilize Kerberos as the underlying technology
for authentication and secure communication.
The LoginModule
documentation describes the interface that must be implemented by
authentication technology providers. LoginModules are
plugged in under applications to provide a particular type of
authentication.
While applications write to the
LoginContext Application Programming Interface (API),
authentication technology providers implement the
LoginModule interface. A
Configuration specifies the LoginModule(s)
to be used with a particular login application. Different
LoginModules can be plugged in under the application
without requiring any modifications to the application itself.
The LoginContext is responsible for reading the
Configuration and instantiating the specified
LoginModules. Each LoginModule is
initialized with a Subject,
a CallbackHandler, shared LoginModule
state, and LoginModule-specific options.
The Subject represents the user or service
currently being authenticated and is updated by a
LoginModule with relevant Principals
and credentials if authentication succeeds.
LoginModules use the
CallbackHandler to communicate with users (to
prompt for user names and passwords, for example), as described in
the login method description. Note that the
CallbackHandler may be null. A
LoginModule that requires a
CallbackHandler to authenticate the
Subject may throw a
LoginException if it was initialized with a
nullCallbackHandler.
LoginModules optionally use the shared state to
share information or data among themselves.
The LoginModule-specific options represent
the options configured for this LoginModule in the
login Configuration. The options are defined by the
LoginModule itself and control the behavior within it.
For example, a LoginModule may define options to
support debugging/testing capabilities. Options are defined using a
key-value syntax, such as debug=true. The
LoginModule stores the options as a Map
so that the values may be retrieved using the key. Note that there
is no limit to the number of options a LoginModule
chooses to define.
The calling application sees the authentication process as a
single operation invoked via a call to the
LoginContext's login method. However, the
authentication process within each LoginModule
proceeds in two distinct phases. In the first phase of
authentication, the LoginContext's login
method invokes the login method of each
LoginModule specified in the
Configuration. The login method for a
LoginModule performs the actual authentication
(prompting for and verifying a password for example) and saves its
authentication status as private state information. Once finished,
the LoginModule's login method returns
true (if it succeeded) or false (if it
should be ignored), or it throws a LoginException to
specify a failure. In the failure case, the
LoginModule must not retry the authentication or
introduce delays. The responsibility of such tasks belongs to the
application. If the application attempts to retry the
authentication, each LoginModule's login
method will be called again.
In the second phase, if the LoginContext's overall
authentication succeeded (calls to the relevant required,
requisite, sufficient and optionalLoginModules' login methods succeeded),
then the commit method for each
LoginModule gets invoked. (For an explanation of the
LoginModule flags required, requisite,
sufficient and optional, please consult the
javax.security.auth.login.Configuration documentation
and Appendix B: Login
Configuration Files in the JAAS Reference Guide.) The
commit method for a LoginModule checks
its privately saved state to see if its own authentication
succeeded. If the overall LoginContext authentication
succeeded and the LoginModule's own authentication
succeeded, then the commit method associates the
relevant Principals (authenticated identities) and
credentials (authentication data such as cryptographic keys) with
the Subject.
If the LoginContext's overall authentication failed
(the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
LoginModules' login methods did not
succeed), then the abort method for each
LoginModule gets invoked. In this case, the
LoginModule removes/destroys any authentication state
originally saved.
Logging out a Subject involves only one phase. The
LoginContext invokes the LoginModule's
logout method. The logout method for the
LoginModule then performs the logout procedures, such
as removing Principals or credentials from the
Subject, or logging session information.
The steps required to implement and test a new
LoginModule follow. Please reference the SampleLoginModule and other
files described in the JAAS Reference
Guide for examples of what may be done for the various
steps.
The first thing you need to do is understand the
authentication technology to be implemented by your new
LoginModule provider, and determine its requirements.
One thing you will need to determine is whether or not your
LoginModule will require some form of user interaction
(retrieving a user name and password, for example). If so, you will
need to become familiar with the CallbackHandler interface and the javax.security.auth.callback package. In that
package you will find several possible Callback
implementations to use. (Alternatively, you can create your own
Callback implementations.) The
LoginModule will invoke the
CallbackHandler specified by the application itself
and passed to the LoginModule's
initialize method. The LoginModule passes
the CallbackHandler an array of appropriate
Callbacks. See the login method
in Step 3.
Note that it is possible for LoginModule
implementations not to have any end-user interactions. Such
LoginModules would not need to access the
callback package.
Another thing you should determine is what configuration options
you want to make available to the user, who specifies configuration
information in whatever form the current Configuration
implementation expects (for example, in files). For each option,
decide the option name and possible values. For example, if a
LoginModule may be configured to consult a particular
authentication server host, decide on the option's key name
("auth_server", for example), as well as the possible server
hostnames valid for that option ("server_one.foo.com" and
"server_two.foo.com", for example).
Decide on the proper package and class name for your
LoginModule.
For example, a LoginModule developed by IBM might
be called com.ibm.auth.Module where
com.ibm.auth is the package name and
Module is the name of the LoginModule
class implementation.
See below and the LoginModule
API for more information on each method above.
In addition to these methods, a LoginModule
implementation must provide a public constructor with no arguments.
This allows for its proper instantiation by a
LoginContext. Note that if no such constructor is
provided in your LoginModule implementation, a default
no-argument constructor is automatically inherited from the
Object class.
The initialize method is called to initialize the
LoginModule with the relevant authentication and state
information.
This method is called by a LoginContext immediately
after this LoginModule has been instantiated, and
prior to any calls to its other public methods. The method
implementation should store away the provided arguments for future
use.
The initialize method may additionally peruse the
provided sharedState to determine what additional
authentication state it was provided by other
LoginModules, and may also traverse through the
provided options to determine what configuration options
were specified to affect the LoginModule's behavior.
It may save option values in variables for future use.
Below is a list of options commonly supported by LoginModules.
Note that the following is simply a guideline. Modules are free to
support a subset (or none) of the following options.
try_first_pass - If true, the first
LoginModule in the stack saves the password entered, and subsequent
LoginModules also try to use it. If authentication fails, the
LoginModules prompt for a new password and retry the
authentication.
use_first_pass - If true, the first
LoginModule in the stack saves the password entered, and subsequent
LoginModules also try to use it. LoginModules do not prompt for a
new password if authentication fails (authentication simply
fails).
try_mapped_pass - If true, the first
LoginModule in the stack saves the password entered, and subsequent
LoginModules attempt to map it into their service-specific
password. If authentication fails, the LoginModules prompt for a
new password and retry the authentication.
use_mapped_pass - If true, the first
LoginModule in the stack saves the password entered, and subsequent
LoginModules attempt to map it into their service-specific
password. LoginModules do not prompt for a new password if
authentication fails (authentication simply fails).
moduleBanner - If true, then when
invoking the CallbackHandler, the LoginModule provides a
TextOutputCallback as the first Callback, which describes the
LoginModule performing the authentication.
debug - If true, instructs a
LoginModule to output debugging information.
The initialize method may freely ignore state or
options it does not understand, although it would be wise to log
such an event if it does occur.
Note that the LoginContext invoking this
LoginModule (and the other configured
LoginModules, as well), all share the same references
to the provided Subject and sharedState.
Modifications to the Subject and
sharedState will, therefore, be seen by all.
boolean login() throws LoginException;
The login method is called to authenticate a
Subject. This is phase 1 of authentication.
This method implementation should perform the actual
authentication. For example, it may cause prompting for a user name
and password, and then attempt to verify the password against a
password database. Another example implementation may inform the
user to insert their finger into a fingerprint reader, and then
match the input fingerprint against a fingerprint database.
If your LoginModule requires some form of user
interaction (retrieving a user name and password, for example), it
should not do so directly. That is because there are various ways
of communicating with a user, and it is desirable for
LoginModules to remain independent of the different
types of user interaction. Rather, the LoginModule's
login method should invoke the handle
method of the CallbackHandler passed to the
initialize method to perform the user interaction and
set appropriate results, such as the user name and password. The
LoginModule passes the CallbackHandler an
array of appropriate Callbacks, for example a NameCallback
for the user name and a
PasswordCallback for the password, and the
CallbackHandler performs the requested user
interaction and sets appropriate values in the
Callbacks. For example, to process a
NameCallback, the CallbackHandler may
prompt for a name, retrieve the value from the user, and call the
NameCallback's setName method to store
the name.
The authentication process may also involve communication over a
network. For example, if this method implementation performs the
equivalent of a kinit in Kerberos, then it would need to
contact the KDC. If a password database entry itself resides in a
remote naming service, then that naming service needs to be
contacted, perhaps via the Java Naming and Directory Interface
(JNDI). Implementations might also interact with an underlying
operating system. For example, if a user has already logged into an
operating system like Solaris or Windows NT, this method might
simply import the underlying operating system's identity
information.
The login method should
Determine whether or not this LoginModule should
be ignored. One example of when it should be ignored is when a user
attempts to authenticate under an identity irrelevant to this
LoginModule (if a user attempts to authenticate as
root using NIS, for example). If this
LoginModule should be ignored, login
should return false. Otherwise, it should do the
following:
Call the CallbackHandlerhandle
method if user interaction is required.
Perform the authentication.
Store the authentication result (success or failure).
If authentication succeeded, save any relevant state
information that may be needed by the commit
method.
Note that the login method implementation should
not associate any new Principal or credential
information with the saved Subject object. This method
merely performs the authentication, and then stores away the
authentication result and corresponding authentication state. This
result and state will later be accessed by the commit
or abort method. Note that the result and state should
typically not be saved in the sharedStateMap,
as they are not intended to be shared with other
LoginModules.
An example of where this method might find it useful to store
state information in the sharedStateMap is
when LoginModules are configured to share passwords.
In this case, the entered password would be saved as shared state.
By sharing passwords, the user only enters the password once, and
can still be authenticated to multiple LoginModules.
The standard conventions for saving and retrieving names and
passwords from the sharedStateMap are the
following:
javax.security.auth.login.name - Use this as the
shared state map key for saving/retrieving a name.
javax.security.auth.login.password - Use this as
the shared state map key for saving/retrieving a password.
If authentication fails, the login method should
not retry the authentication. This is the responsibility of the
application. Multiple LoginContextlogin
method calls by an application are preferred over multiple login
attempts from within LoginModule.login().
boolean commit() throws LoginException;
The commit method is called to commit the
authentication process. This is phase 2 of authentication when
phase 1 succeeds. It is called if the LoginContext's
overall authentication succeeded (that is, if the relevant
REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
LoginModules succeeded.)
This method should access the authentication result and
corresponding authentication state saved by the login
method.
If the authentication result denotes that the login
method failed, then this commit method should
remove/destroy any corresponding state that was originally
saved.
If the saved result instead denotes that this
LoginModule's login method succeeded,
then the corresponding state information should be accessed to
build any relevant Principal and credential
information. Such Principals and credentials should
then be added to the Subject stored away by the
initialize method.
After adding Principals and credentials,
dispensable state fields should be destroyed expeditiously. Likely
fields to destroy would be user names and passwords stored during
the authentication process.
The commit method should save private state
indicating whether the commit succeeded or failed.
The following chart depicts what a LoginModule's
commit method should return. The different boxes
represent the different situations that may occur. For example, the
top-left corner box depicts what the commit method
should return if both the previous call to login
succeeded and the commit method itself succeeded.
The abort method is called to abort the
authentication process. This is phase 2 of authentication when
phase 1 fails. It is called if the LoginContext's
overall authentication failed.
This method first accesses this LoginModule's
authentication result and corresponding authentication state saved
by the login (and possibly commit)
methods, and then clears out and destroys the information. Sample
state to destroy would be user names and passwords.
If this LoginModule's authentication attempt
failed, then there shouldn't be any private state to clean up.
The following charts depict what a LoginModule's
abort method should return. This first chart assumes
that the previous call to login succeeded. For
instance, the top-left corner box depicts what the
abort method should return if both the previous call
to login and commit succeeded, and the
abort method itself also succeeded.
The second chart depicts what a LoginModule's
abort method should return, assuming that the previous
call to login failed. For instance, the top-left
corner box depicts what the abort method should return
if the previous call to login failed, the previous
call to commit succeeded, and the abort
method itself also succeeded.
This method removes Principals, and
removes/destroys credentials associated with the
Subject during the commit operation. This
method should not touch those Principals or
credentials previously existing in the Subject, or
those added by other LoginModules.
If the Subject has been marked read-only
(the Subject's isReadOnly method returns
true), then this method should only destroy credentials
associated with the Subject during the
commit operation (removing the credentials is not
possible). If the Subject has been marked as
read-only and the credentials associated with the
Subject during the commit operation are
not destroyable (they do not implement the Destroyable
interface), then this method may throw a
LoginException.
The logout method should return true
if logout succeeds, or otherwise throw a LoginException.
Either choose an existing sample application for your testing,
or write a new one. See JAAS Reference
Guide for information about application requirements and a
sample application you can use for your testing.
Place your LoginModule and application code in
separate JAR files, in preparation for referencing the JAR files in
the policy in Step 6c. Here is a sample
command for creating a JAR file:
jar cvf <JAR file name> <list of classes, separated by spaces>
This command creates a JAR file with the specified name
containing the specified classes.
The application can be stored essentially anywhere you like.
Your LoginModule can also be placed anywhere you
(and other clients) like. If the LoginModule is fully
trusted, it can be placed in the JRE's lib/ext
(standard extension) directory.
You will need to test the LoginModule being located
both in the lib/ext directory and elsewhere because in
one situation your LoginModule will need to explicitly
be granted permissions required
for any security-sensitive operations it does, while in the other
case such permissions are not needed.
If your LoginModule is placed in the JRE's
lib/ext directory, it will be treated as an installed
extension and no permissions need to be granted, since the default
system policy file grants all
permissions to installed extensions.
If your LoginModule is placed anywhere else, the
permissions need to be granted, for example by grant
statements in a policy file.
Decide where you will store the LoginModule JAR
file for testing the case where it is not an installed extension.
In the next step, you grant permissions to the JAR file, in the
specified location.
If your LoginModule and/or application performs
security-sensitive tasks that will trigger security checks (making
network connections, reading or writing files on a local disk,
etc), it will need to be granted the required permissions if it is not an installed
extension (see Step 6b) and it is run
while a security manager is installed.
Since LoginModules usually associate
Principals and credentials with an authenticated
Subject, some types of permissions a LoginModule will
typically require are AuthPermissions
with target names "modifyPrincipals", "modifyPublicCredentials",
and "modifyPrivateCredentials".
A sample statement granting permissions to a
LoginModule whose code is in MyLM.jar
appears below. Such a statement could appear in a policy file. In
this example, the MyLM.jar file is assumed to be in
the /localWork directory.
Note: Since a LoginModule is always invoked within
an AccessController.doPrivileged call, it should not
have to call doPrivileged itself. If it does, it may
inadvertently open up a security hole. For example, a
LoginModule that invokes the application-provided
CallbackHandler inside a doPrivileged
call opens up a security hole by permitting the application's
CallbackHandler to gain access to resources it would
otherwise not have been able to access.
Because JAAS supports a pluggable authentication architecture,
your new LoginModule can be used without requiring
modifications to existing applications. Only the login
Configuration needs to be updated in order to indicate
use of a new LoginModule.
The default Configuration implementation from Sun
Microsystems reads configuration information from configuration
files, as described in
com.sun.security.auth.login.ConfigFile.html.
Create a configuration file to be used for testing. For example,
to configure the previously-mentioned hypothetical IBM
LoginModule for an application, the configuration file
might look like this:
where AppName should be whatever name the application
uses to refer to this entry in the login configuration file. The
application specifies this name as the first argument to the
LoginContext constructor.
Finally, test your application and its use of the
LoginModule. When you run the application, specify the
login configuration file to be used. For example, suppose your
application is named MyApp, it is located in
MyApp.jar, and your configuration file is
test.conf. You could run the application and specify
the configuration file via the following:
You may want to configure the LoginModule with a
debug option to help ensure that it is working
correctly.
Debug your code and continue testing as needed. If you have
problems, review the steps above and ensure they are all
completed.
Be sure to vary user input and the LoginModule
options specified in the configuration file.
Be sure to also include testing using different installation
options (e.g., making the LoginModule an installed
extension or placing it on the class path) and execution
environments (with or without a security manager running).
Installation options are discussed in Step
6b. In particular, in order to ensure your
LoginModule works when a security manager is installed
and the LoginModule and application are not installed
extensions, you need to test such an installation and execution
environment, after granting required permissions, as described in
Step 6c.
If you find during testing that your LoginModule or
application needs modifications, make the modifications, recompile
(Step 5), place the updated code in a JAR
file (Step 6a), re-install the JAR file
(Step 6b), if needed fix or add to the
permissions (Step 6c), if needed modify
the login configuration file (Step 6d),
and then re-run the application and repeat these steps as
needed.
The next step is to write documentation for clients of your
LoginModule. Example documentation you may want to
include is:
A README or User Guide describing
the authentication process employed by your
LoginModule implementation.
information on how to install the
LoginModule.
configuration options accepted by the LoginModule.
For each option, specify the option name and possible values (or
types of values), as well as the behavior the option controls.
the permissions required by your LoginModule when
it is run with a security manager (and it is not an installed
extension).
An example Configuration file that references your
new LoginModule.
An example policy file granting your LoginModule
the required permissions.
API documentation. Putting javadoc comments into your
source code as you write it will make the API javadocs easy to
generate.