The Java Authentication and Authorization Service (JAAS) was introduced as an optional package to the Java 2 SDK, Standard Edition (J2SDK), v 1.3. JAAS was integrated into the Java Standard Edition Development Kit starting with J2SDK 1.4.
JAAS can be used for two purposes:
This section provides a basic tutorial for the authentication component. The authorization component will be described in the JAAS Authorization Tutorial.
JAAS authentication is performed in a pluggable fashion. This permits Java applications to remain independent from underlying authentication technologies. New or updated technologies can be plugged in without requiring modifications to the application itself. An implementation for a particular authentication technology to be used is determined at runtime. The implementation is specified in a login configuration file. The authentication technology used for this tutorial is very basic, just ensuring that the user specifies a particular name and password.
The rest of this tutorial consists of the following sections:
If you want to first see the tutorial code in action, you can skip directly to Running the Code and then go back to the other sections to learn about coding and configuration file details.
The code for this tutorial consists of three files:
Our authentication tutorial application code is contained in a single source file, SampleAcn.java. That file contains two classes:
main
method of the SampleAcn class performs the
authentication and then reports whether or not authentication
succeeded.
The code for authenticating the user is very simple, consisting of just two steps:
First the basic code is shown, followed by a complete listing of the SampleAcn
class, complete with the import statement it requires and error
handling.
In order to authenticate a user, you first need a
javax.security.auth.login.LoginContext
. Here is the
basic way to instantiate a LoginContext:
import javax.security.auth.login.*; . . . LoginContext lc = new LoginContext(<config file entry name>, <CallbackHandler to be used for user interaction>);and here is the specific way our tutorial code does the instantiation:
import javax.security.auth.login.*; . . . LoginContext lc = new LoginContext("Sample", new MyCallbackHandler());
The arguments are the following:
This is the name for the LoginContext to use to look up an
entry for this application in the JAAS login configuration file,
described here. Such an entry specifies
the class(es) that implement the desired underlying
authentication technology(ies). The class(es) must implement the
LoginModule interface, which is in the
javax.security.auth.spi
package.
In our sample code, we use the SampleLoginModule
supplied with this tutorial. The SampleLoginModule performs
authentication by ensuring that the user types a particular name
and password.
The entry in the login configuration file we use for this tutorial (see sample_jaas.config) has the name "Sample", so that is the name we specify as the first argument to the LoginContext constructor.
When a LoginModule needs to communicate with the user, for
example to ask for a user name and password, it does 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 invokes a
javax.security.auth.callback.CallbackHandler
to
perform the user interaction and obtain the requested
information, such as the user name and password.
An instance of the particular CallbackHandler to be used is
specified as the second argument to the LoginContext constructor.
The LoginContext forwards that instance to the underlying
LoginModule (in our case SampleLoginModule). An application
typically provides its own CallbackHandler implementation. Two
simple CallbackHandlers, TextCallbackHandler and
DialogCallbackHandler, are provided in the
com.sun.security.auth.callback
package as sample
implementations. For this tutorial we could use the
TextCallbackHandler, which outputs information to and reads input
from the command line. However, we instead demonstrate the more
typical case of an application providing its own CallbackHandler
implementation, described in The
MyCallbackHandler Class.
login
MethodOnce we have a LoginContext lc
, we can call its
login
method to carry out the authentication
process:
lc.login();
The LoginContext instantiates a new empty javax.security.auth.Subject
object (which represents the user or service being
authenticated). The LoginContext constructs the configured
LoginModule (in our case SampleLoginModule) and initializes it
with this new Subject and MyCallbackHandler.
The LoginContext's login
method then calls
methods in the SampleLoginModule to perform the login and
authentication. The SampleLoginModule will utilize the
MyCallbackHandler to obtain the user name and password. Then the
SampleLoginModule will check that the name and password are the
ones it expects.
If authentication is successful, the SampleLoginModule populates the Subject with a Principal representing the user. The Principal the SampleLoginModule places in the Subject is an instance of SamplePrincipal, which is a sample class implementing the java.security.Principal interface.
See Subjects, Principals, Authentication, and Credentials for information on these terms.
The calling application can subsequently retrieve the
authenticated Subject by calling the LoginContext's
getSubject
method, although doing so is not
necessary for this tutorial.
SampleAcn
Class CodeNow that you have seen the basic code required to authenticate the user, we can put it all together into the full class in SampleAcn.java, which includes relevant import statements and error handling:
package sample; import javax.security.auth.login.*; // . . . other import statements needed by MyCallbackHandler . . . /** * This Sample application attempts to authenticate a user * and reports whether or not the authentication was * successful. */ public class SampleAcn { /** * Attempt to authenticate the user. * * @param args input arguments for this application. * These are ignored. */ public static void main(String[] args) { // Obtain a LoginContext, needed for authentication. // Tell it to use the LoginModule implementation // specified by the entry named "Sample" in the // JAAS login configuration file and to also use the // specified CallbackHandler. LoginContext lc = null; try { lc = new LoginContext("Sample", new MyCallbackHandler()); } catch (LoginException le) { System.err.println("Cannot create LoginContext. " + le.getMessage()); System.exit(-1); } catch (SecurityException se) { System.err.println("Cannot create LoginContext. " + se.getMessage()); System.exit(-1); } // the user has 3 attempts to authenticate successfully int i; for (i = 0; i < 3; i++) { try { // attempt authentication lc.login(); // if we return with no exception, // authentication succeeded break; } catch (LoginException le) { System.err.println("Authentication failed:"); System.err.println(" " + le.getMessage()); try { Thread.currentThread().sleep(3000); } catch (Exception e) { // ignore } } } // did they fail three times? if (i == 3) { System.out.println("Sorry"); System.exit(-1); } System.out.println("Authentication succeeded!"); } }
In some cases a LoginModule must communicate with the user to
obtain authentication information. LoginModules use a
javax.security.auth.callback.CallbackHandler
for
this purpose. An application can either use one of the sample
implementations provided in the
com.sun.security.auth.callback
package or, more
typically, write a CallbackHandler implementation. The
application passes the CallbackHandler as an argument to the
LoginContext instantiation. The LoginContext forwards the
CallbackHandler directly to the underlying LoginModules.
The tutorial sample code supplies its own CallbackHandler implementation, the MyCallbackHandler class in SampleAcn.java.
CallbackHandler is an interface with one method to implement:
void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException;
The LoginModule passes the CallbackHandler handle method an
array of appropriate
javax.security.auth.callback.Callback
s, 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.
The MyCallbackHandler handle
method is structured
as follows:
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof TextOutputCallback) { // display a message according to a specified type . . . } else if (callbacks[i] instanceof NameCallback) { // prompt the user for a username . . . } else if (callbacks[i] instanceof PasswordCallback) { // prompt the user for a password . . . } else { throw new UnsupportedCallbackException (callbacks[i], "Unrecognized Callback"); } } }
A CallbackHandler handle
method is passed an
array of Callback instances, each of a particular type
(NameCallback, PasswordCallback, etc.). It must handle each
Callback, performing user interaction in a way that is
appropriate for the executing application.
MyCallbackHandler handles three types of Callbacks: NameCallback to prompt the user for a user name, PasswordCallback to prompt for a password, and TextOutputCallback to report any error, warning, or other messages the SampleLoginModule wishes to send to the user.
The handle
method handles a
TextOutputCallback by extracting the message to be
reported and then printing it to System.out
,
optionally preceded by additional wording that depends on the
message type. The message to be reported is determined by calling
the TextOutputCallback's getMessage
method and the
type by calling its getMessageType
method. Here is
the code for handling a TextOutputCallback:
if (callbacks[i] instanceof TextOutputCallback) { // display the message according to the specified type TextOutputCallback toc = (TextOutputCallback)callbacks[i]; switch (toc.getMessageType()) { case TextOutputCallback.INFORMATION: System.out.println(toc.getMessage()); break; case TextOutputCallback.ERROR: System.out.println("ERROR: " + toc.getMessage()); break; case TextOutputCallback.WARNING: System.out.println("WARNING: " + toc.getMessage()); break; default: throw new IOException("Unsupported message type: " + toc.getMessageType()); }
The handle
method handles a NameCallback
by prompting the user for a user name. It does this by printing
the prompt to System.err
. It then sets the name for
use by the SampleLoginModule by calling the NameCallback's
setName
method, passing it the name typed by the
user:
} else if (callbacks[i] instanceof NameCallback) { // prompt the user for a username NameCallback nc = (NameCallback)callbacks[i]; System.err.print(nc.getPrompt()); System.err.flush(); nc.setName((new BufferedReader (new InputStreamReader(System.in))).readLine());
Similarly, the handle
method handles a
PasswordCallback by printing a prompt to
System.err
to prompt the user for a password. It
then sets the password for use by the SampleLoginModule by
calling the PasswordCallback's setPassword
method,
passing it the password typed by the user, which it reads by
calling the readPassword
method (shown below):
} else if (callbacks[i] instanceof PasswordCallback) { // prompt the user for sensitive information PasswordCallback pc = (PasswordCallback)callbacks[i]; System.err.print(pc.getPrompt()); System.err.flush(); pc.setPassword(readPassword(System.in));
The readPassword
method is a basic method for
reading a password from an InputStream:
private char[] readPassword(InputStream in) throws IOException { char[] lineBuffer; char[] buf; int i; buf = lineBuffer = new char[128]; int room = buf.length; int offset = 0; int c; loop: while (true) { switch (c = in.read()) { case -1: case '\n': break loop; case '\r': int c2 = in.read(); if ((c2 != '\n') && (c2 != -1)) { if (!(in instanceof PushbackInputStream)) { in = new PushbackInputStream(in); } ((PushbackInputStream)in).unread(c2); } else break loop; default: if (--room < 0) { buf = new char[offset + 128]; room = buf.length - offset - 1; System.arraycopy(lineBuffer, 0, buf, 0, offset); Arrays.fill(lineBuffer, ' '); lineBuffer = buf; } buf[offset++] = (char) c; break; } } if (offset == 0) { return null; } char[] ret = new char[offset]; System.arraycopy(buf, 0, ret, 0, offset); Arrays.fill(buf, ' '); return ret; } }
SampleLoginModule.java
implements the
LoginModule
interface. SampleLoginModule is the class
specified by the tutorial's login
configuration file as the class implementing the desired
underlying authentication. SampleLoginModule's user
authentication consists of simply verifying that the name and
password specified by the user have specific values. This
SampleLoginModule is specified by the tutorial's login
configuration file as the LoginModule to use because (1) It
performs a basic type of authentication suitable for any
environment and thus is appropriate for a tutorial for all users,
and (2) It provides an example LoginModule implementation for
experienced programmers who require the ability to write a
LoginModule implementing an authentication technology.
SamplePrincipal.java is a sample class implementing the java.security.Principal interface. If authentication is successful, the SampleLoginModule populates a Subject with a SamplePrincipal representing the user.
Important: If you are an application writer, you do not need to know how to write a LoginModule or a Principal implementation. You do not need to examine the SampleLoginModule or SamplePrincipal code. All you have to know is how to write your application and specify configuration information (such as in a login configuration file) such that the application will be able to utilize the LoginModule specified by the configuration to authenticate the user. You need to determine which LoginModule(s) you want to use and read the LoginModule's documentation to learn about what options you can specify values for (in the configuration) to control the LoginModule's behavior.
Any vendor can provide a LoginModule implementation that you can use. Some implementations are supplied with the JRE from Sun Microsystems, as listed in the JAAS Login Configuration File document.
Information for programmers who want to write a LoginModule
can be found in the JAAS
LoginModule
Developer's Guide.
JAAS authentication is performed in a pluggable fashion, so
applications can remain independent from underlying
authentication technologies. A system administrator determines
the authentication technologies, or LoginModules, to be used for
each application and configures them in a login Configuration.
The source of the configuration information (for example, a file
or a database) is up to the current
javax.security.auth.login.Configuration implementation. The
default Configuration
implementation from Sun
Microsystems reads configuration information from configuration
files, as described in
com.sun.security.auth.login.ConfigFile.html.
See JAAS Login Configuration File for information as to what a login configuration file is, what it contains, and how to specify which login configuration file should be used.
As noted, the login configuration file we use for this tutorial, sample_jaas.config, contains just one entry, which is
Sample { sample.module.SampleLoginModule required debug=true; };
This entry is named "Sample" and that is the name that our
tutorial application, SampleAcn
, uses to refer to
this entry. The entry specifies that the LoginModule to be used
to do the user authentication is the SampleLoginModule in the
sample.module
package and that this
SampleLoginModule is required to "succeed" in order for
authentication to be considered successful. The SampleLoginModule
succeeds only if the name and password supplied by the user are
the one it expects ("testUser" and "testPassword",
respectively).
The SampleLoginModule also defines a "debug" option that can
be set to true
as shown. If this option is set to
true,
SampleLoginModule outputs extra information
about the progress of authentication. A LoginModule can define as
many options as it wants. The LoginModule documentation should
specify the possible option names and values you can set in your
configuration file.
To execute our JAAS authentication tutorial code, all you have to do is
SampleAcn.java
, are in a package named
sample
):
sample.module
):
sample.principal
):
SampleAcn.java
, SampleLoginModule.java
,
and SamplePrincipal.java
:
javac sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java(Type all that on one line.)
SampleAcn
application, specifying
-Djava.security.auth.login.config==sample_jaas.config
that the login configuration file to be used is
sample_jaas.config
.The full command is below.
java -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
You will be prompted for your user name and password, and the SampleLoginModule specified in the login configuration file will check to ensure these are correct. The SampleLoginModule expects "testUser" for the user name and "testPassword" for the password.
You will see some messages output by SampleLoginModule as a
result of the debug
option being set to
true
in the login configuration file. Then, if your
login is successful, you will see the following message output by
SampleAcn:
Authentication succeeded!If the login is not successful (for example, if you misspell the password), you will see
Authentication failed:followed by a reason for the failure. For example, if you mistype the password, you may see a message like the following:
Authentication failed: Password Incorrect
SampleAcn gives you three chances to successfully log in.
When a Java program is run with a security manager installed, the program is not allowed to access resources or otherwise perform security-sensitive operations unless it is explicitly granted permission to do so by the security policy in effect. In Java platforms that are compatible with J2SE v 1.2 and later, the permission must be granted by an entry in a policy file.
Most browsers install a security manager, so applets
typically run under the scrutiny of a security manager.
Applications, on the other hand, do not, since a security
manager is not automatically installed when an application is
running. Thus an application, like our SampleAcn
application, by default has full access to resources.
To run an application with a security manager, simply
invoke the interpreter with a
-Djava.security.manager
argument included on the
command line.
If you try invoking SampleAcn
with a security
manager but without specifying any policy file, you will get the
following (unless you have a default policy setup elsewhere that
grants the required permissions or grants
AllPermission
):
% java -Djava.security.manager \ -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn Exception in thread "main" java.security.AccessControlException: access denied ( javax.security.auth.AuthPermission createLoginContext.Sample)
As you can see, you get an AccessControlException, because we haven't created and used a policy file granting our code the permission that is required in order to be allowed to create a LoginContext.
Here are the complete steps required in order to be able to
run our SampleAcn
application with a security
manager installed. You can skip the first five steps if you have
already done them, as described in Running the
Code.
SampleAcn.java
, are in a package named
sample
):
sample.module
):
sample.principal
):
SampleAcn.java
, SampleLoginModule.java
,
and SamplePrincipal.java
:
javac sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java(Type all that on one line.)
SampleAcn.class
and
MyCallbackHandler.class
:
jar -cvf SampleAcn.jar sample/SampleAcn.class sample/MyCallbackHandler.class
(Type all that on one line.) This command creates a JAR file,
SampleAcn.jar
, and places the
SampleAcn.class
and
MyCallbackHandler.class
files inside it.
SampleLoginModule.class
and
SamplePrincipal.class
:
jar -cvf SampleLM.jar sample/module/SampleLoginModule.class sample/principal/SamplePrincipal.class
The permission that is needed by code attempting to
instantiate a LoginContext is a
javax.security.auth.AuthPermission
with target
"createLoginContext.<entry name>". Here, <entry name>
refers to the name of the login configuration file entry that the
application references in its instantiation of LoginContext. The
name used by our SampleAcn
application's
LoginContext instantiation is "Sample", as you can see in the
code:
LoginContext lc = new LoginContext("Sample", new MyCallbackHandler());
Thus, the permission that needs to be granted to
SampleAcn.jar
is
permission javax.security.auth.AuthPermission "createLoginContext.Sample";
The SampleLM.jar
file also needs to be granted a
permission. The documentation for a LoginModule should tell you
what permissions it needs to be granted. In the case of
SampleLoginModule, it needs a
javax.security.auth.AuthPermission
with target
"modifyPrincipals" in order to populate a Subject with a
Principal:
permission javax.security.auth.AuthPermission "modifyPrincipals";
Copy the policy file sampleacn.policy to the same directory as
that in which you stored SampleAcn.java
, etc. The
policy file contains the following grant
statement
to grant SampleAcn.jar
(in the current directory)
its required permission:
grant codebase "file:./SampleAcn.jar" { permission javax.security.auth.AuthPermission "createLoginContext.Sample"; };
The policy file also contains the following grant
statement to grant SampleLM.jar
(also in the current
directory) its required permission:
grant codebase "file:./SampleLM.jar" { permission javax.security.auth.AuthPermission "modifyPrincipals"; };
Note: Policy files and the structure of entries within them are described in Default Policy Implementation and Policy File Syntax. Permissions are described here.
SampleAcn
application, specifying
-classpath
clause that classes
should be searched for in the SampleAcn.jar
and
SampleLM.jar
JAR files,-Djava.security.manager
that a security
manager should be installed,-Djava.security.policy==sampleacn.policy
that
the policy file to be used is sampleacn.policy
,
and-Djava.security.auth.login.config==sample_jaas.config
that the login configuration file to be used is
sample_jaas.config
.Below are the full commands to use for both Microsoft Windows and Unix systems. The only difference is that on Microsoft Windows systems you use semicolons to separate classpath items, while you use colons for that purpose on Unix systems.
Here is the full command for Microsoft Windows systems:
java -classpath SampleAcn.jar;SampleLM.jar -Djava.security.manager -Djava.security.policy==sampleacn.policy \ -Djava.security.auth.login.config==sample_jaas.config \ sample.SampleAcn
Here is the full command for UNIX systems:
java -classpath SampleAcn.jar:SampleLM.jar -Djava.security.manager -Djava.security.policy==sampleacn.policy \ -Djava.security.auth.login.config==sample_jaas.config \ sample.SampleAcn
Type all that on one line. Multiple lines are used here for legibility. If the command is too long for your system, you may need to place it in a .bat file (for Microsoft Windows) or a .sh file (for UNIX) and then run that file to execute the command.
Since the specified policy file contains an entry granting the code the required permissions, execution should proceed without any exceptions indicating a required permission was not granted. You will be prompted for a user name and password (use "testUser" and "testPassword"), and the SampleLoginModule specified in the login configuration file will check the name and password. If your login is successful, you will see the message "Authentication succeeded!" and if not, you will see "Authentication failed:" followed by a reason for the failure.