SummaryBy John Musser and Paul Feuer
JAAS, the Java Authentication and Authorization Service API, provides flexible and scalable mechanisms for securing your client- and server-side Java applications. Where earlier Java security frameworks focused on protecting the user from mobile code, based on the code's origins and who created it, JAAS protects the system from users, based on who runs the code and their permissions. This pluggable, stackable API lets you incorporate standard security mechanisms like Solaris NIS (Network Information Services), Windows NT, LDAP (lightweight directory access protocol), Kerberos, and others into your application in a consistent, configurable way. This article first introduces you to the core elements of JAAS authentication. It then shows you how to develop your own custom login modules, using both command-line- and JSP (JavaServer Page)-based examples that authenticate against user records stored in a JDBC (Java Database Connectivity)-compatible RDBMS (relational database management system). (3,600 words; September 13, 2002)
Have you ever needed to create a login authentication mechanism for an application? Odds are, you have, and probably more than once, with each new implementation being close, but not identical, to the previous one. For example, one implementation might use an Oracle database, another might use an NT authentication, and another, an LDAP (lightweight access directory protocol) directory. Wouldn't it be nice to support all these security mechanisms without changing any application-level code?
Now in the Java world, you can with the Java Authentication and Authorization
Service (JAAS). This relatively new API was an extension in J2SE (Java 2
Platform, Standard Edition) 1.3, is a core API in J2SE 1.4, and is also part of
the J2EE (Java 2 Platform, Enterprise Edition) 1.3 specification. In this
article, we'll teach you JAAS essentials and show you how to effectively apply
JAAS to real-world applications. We based this article's application on our own
experiences integrating JAAS into an existing Java Web-based system that used an
RDBMS (relational database management system) for storing user login
information. With JAAS, we designed more robust, flexible, and consistent login
and authentication mechanisms.
Java Authentication and Authorization: The big picture
Before JAAS, Java's security model was mostly shaped by its
origin as a platform-independent language for distributed, networked
applications. In its early days, Java often appeared as mobile code, such as
browser-based applets, and therefore, the initial security model focused on
protecting users based on where the code originated and who created
it. Early Java security mechanisms such as SecurityManager
s,
the sandbox concept, code signing, and policy files were all intended to protect
users from the system.
The invention of JAAS reflects Java's evolution into a general-purpose programming language, used for implementing traditional client and server applications that require login and access control. JAAS protects the system from users by allowing or denying access based upon who or what runs the program. While JAAS can perform both authentication and authorization, in this article, we focus primarily on authentication.
JAAS can simplify your Java security development by putting an abstraction layer between your application and disparate underlying authentication and authorization mechanisms. This independence from platforms and algorithms allows you to use different security mechanisms without modifying your application-level code. As with most Java security APIs, JAAS achieves this implementation-independence through an extensible framework of pluggable service provider interfaces (SPIs): a set of abstract classes and interfaces to which specific implementations are developed.
Figure 1 below gives a high-level overview of how JAAS achieves this
pluggability. Your application-layer code deals primarily with a
LoginContext
. Underneath that LoginContext
is a set of
one or more dynamically configured LoginModule
s, which handle the
actual authentication using the appropriate security infrastructure.
Figure 1. JAAS high-level architecture |
JAAS provides some reference LoginModule
implementations, such
as the JndiLoginModule
; you can also develop your own, as we'll do
here with the RdbmsLoginModule
. We'll also show how you can quickly
set up an application with a choice of implementations using a simple
configuration file.
In addition to being pluggable, JAAS is stackable: in the context of a single login, a set of security modules can stack on top of one another, each called in order and each interacting with a different security infrastructure.
JAAS aspects are modeled on some familiar security architectural patterns and
existing frameworks. The stackable feature, for example, deliberately resembles
the Unix Pluggable Authentication Module (PAM) framework. From a transactional
viewpoint, JAAS adopts behaviors similar to two-phase commit (2PC) protocols.
JAAS's security configuration concepts, including Policy
files and
Permissions
, come from the J2SE 1.2 security packages. JAAS also
borrows ideas from other established security frameworks, such as X.509
certificates, from which the name Subject
is derived (you'll learn
more about Subject
later).
Note: JAAS is just one of several new Java security APIs.
Client- and server-side JAAS
You
can apply JAAS on both the client and the server. Using it on the client side is
straightforward, as we'll demonstrate shortly. On the server-side things grow a
bit more complex. Currently, JAAS in the application server market is a bit
inconsistent; J2EE app servers use JAAS slightly differently, depending on which
one you use. For example, JBossSX, using their own architecture, nicely
integrates JAAS into its overall security framework.
However, though WebLogic 6.x supports JAAS, the details differ.
So you can understand JAAS from both server- and client-side perspectives, we'll demonstrate examples of both in this article. And for the purposes of simplicity on the server, we'll use the Resin application server so we can start off with a cleaner slate (Resin does have a pluggable authentication scheme of its own, but it's nonstandard, so using JAAS gives us more portability options later).
Core JAAS
To get started with JAAS,
you must first ensure it's installed. J2SE 1.4 already includes JAAS; J2SE 1.3
does not. If you want to continue to use J2SE 1.3, download JAAS from Sun
Microsystems WebSite. Once you download and install JAAS to a given directory, you
will see a subdirectory called lib
, which contains one file named
jaas.jar
. You'll need to add this file to your classpath or copy it
to your JRE (Java Runtime Environment) extensions directory (in
<jre-home>\lib\ext
, where <jre-home>
is
your JRE's location). You are then JAAS-ready. Note: If you use
an application server, it may already include JAAS. Check your server's
documentation for details.
With any of these approaches, note that you can change some of the
JAAS-related system property settings (as well as many other Java security
settings) in the Java security properties file. This file,
java.security
, is located in the
<jre-home>/lib/security
directory and written in the standard
Java properties file format.
Using JAAS authentication from your application typically involves the following steps:
LoginContext
CallbackHandler
to the
LoginContext
, for gathering or processing authentication data
LoginContext
's
login()
method
Subject
(assuming login succeeds)Here's a minimal example:
LoginContext lc = new
LoginContext("MyExample");
try
{
lc.login();
}
catch (LoginException) {
//
Authentication
failed.
}
//
Authentication successful, we can now continue.
// We
can use the returned Subject if we like.
Subject sub
= lc.getSubject();
Subject.doAs(sub, new
MyPrivilegedAction());
Underneath the covers, a few other things occur:
LoginContext
finds the
configuration entry "MyExample"
in a JAAS configuration file
(which you configured) to determine which LoginModule
s to load
(see Figure 2)
LoginContext
calls each
LoginModule
's login()
method
login()
method performs the authentication or enlists a
CallbackHandler
CallbackHandler
uses one or more Callback
s
to interact with the user and gather input
Subject
instance is populated with authentication
details such as Principal
s and credentialsWe'll explain further details below, but to begin, let's look at the key JAAS classes and interfaces involved in the process. These are typically divided into the following three groups:
Common | Subject , Principal , credential (credential
is not any specific class, but can be any object) |
Authentication | LoginContext , LoginModule ,
CallbackHandler , Callback |
Authorization | Policy , AuthPermission ,
PrivateCredentialPermission |
Most of these classes and interfaces are in the
javax.security.auth
package's subpackages, with some prebuilt
implementations in the com.sun.security.auth
package, included only
in J2SE 1.4.
Note: Because we focus on authentication in this article, we don't delve into the authorization classes.
Common: Subjects, Principals, and Credentials
The
Subject
class represents an authenticated entity: an end-user or
administrator, or a Web service, device, or another process. The class contains
three sets of security information types:
Principal
s
Principal
s represent Subject
identities. They
implement the java.security.Principal
interface (which predates
JAAS) and java.io.Serializable
. A Subject
's most
important method is getName()
, which returns an identity's string
name. Since a Subject
instance contains an array of
Principal
s, it can thus have multiple names. Because a social
security number, login ID, email address, and so on, can all represent one user,
multiple identities prove common in the real world.
The last element here, credential, is not a class or an interface, but can be
any object. Credentials can include any authentication artifact, such as a
ticket, key, or password, that specific security systems might require. The
Subject
class maintains unique Set
s of private and
public credentials, which can be retrieved with methods such as
getPrivateCredentials()
and getPublicCrendentials()
.
These methods are more often used by security subsystems than at the application
layer.
Authentication: LoginContext
Your application layer uses
LoginContext
as its primary class for authenticating
Subject
s. LoginContext
also represents where JAAS's
dynamic pluggability comes into play, because when you construct a
LoginContext
, you specify a named configuration to load. The
LoginContext
typically loads the configuration information from a
text file, which in turn tells the LoginContext
which
LoginModule
s to use during login.
The three commonly used methods in LoginContext
are:
login() |
Performs login, a relatively complex step that invokes all
LoginModule s specified for this configuration. If it
succeeds, it creates an authenticated Subject . If it fails,
it throws a LoginException . |
getSubject() |
Returns the authenticated Subject . |
logout() |
Logs out the authenticated Subject and removes its
Principal s and credentials. |
We will show how to use these methods later.
Authentication: LoginModule LoginModule
is
the interface to specific authentication mechanisms. J2SE 1.4 ships with a set
of ready-to-use LoginModules
, including:
JndiLoginModule |
Verifies against a directory service configured under JNDI (Java Naming and Directory Interface) |
Krb5LoginModule |
Authenticates using Kerberos protocols |
NTLoginModule |
Uses the current user's NT security information to authenticate |
UnixLoginModule |
Uses the current user's Unix security information to authenticate |
Along with these modules comes a set of corresponding concrete
Principal
implementations in the com.sun.security.auth
package, such as NTDomainPrincipal
and UnixPrincipal
.
The LoginModule
interface has five methods:
initialize() |
Called after the LoginModule is constructed. |
|
Performs the authentication. |
commit() |
Called by the LoginContext after it has accepted the
results from all LoginModule s defined for this application.
We assign Principal s and credentials to the
Subject here. |
abort() |
Called when any LoginModule for this application fails
(even though earlier ones in sequence may have succeeded—thus akin to a
2PC model). No Principal s or credentials are assigned to the
Subject . |
logout() |
Removes the Principal s and credentials associated with
the Subject |
The application layer calls none of these methods directly—the
LoginContext
invokes them as needed. Our example below will
elaborate on these methods' implementations.
Authentication: CallbackHandlers and Callbacks
CallbackHandler
s and Callback
s let a
LoginModule
gather necessary authentication information from a user
or system, while remaining independent of the actual interaction mechanism.
We'll leverage that capability in our design—our RdbmsLoginModule
does not depend on how the user credentials (username/password) are obtained and
can thus be used in the different application environments we will illustrate
(either from the command line or from a JSP).
JAAS comes with seven built-in Callback
s in the
javax.security.auth.callback
package: ChoiceCallback
,
ConfirmationCallback
, LocaleCallback
,
NameCallback
, PasswordCallback
,
TextInputCallback
, TextOutputCallback
. And J2SE 1.4
supplies two CallbackHandler
s in the
com.sun.security.auth.callback
package:
DialogCallbackHander
and TextCallbackHandler
. Many
Callback
s resemble each other, and all are oriented towards
client-side scenarios. We'll show you below how to develop your own
CallbackHandler
later.
Configuration files
As we mentioned, much of JAAS's
extensibility comes from its ability to be dynamically configured. This
configuration is typically specified through a text file consisting of one or
more configuration blocks, called applications. Each application is a
set of one or more specified LoginModule
s.
When your code instantiates a LoginContext
, you pass it the name
of one of the application blocks in the configuration file. The
LoginContext
will use LoginModule
s based on what you
specify in the application entry. These specifications drive which
LoginModule
s are invoked, in which order, and according to which
rules.
The configuration file has the following structure:
Application
{
ModuleClass Flag ModuleOptions;
ModuleClass Flag ModuleOptions;
...
};
Application
{
ModuleClass Flag ModuleOptions;
...
};
...
Here's an example of an application block named Sample
:
Sample
{
com.sun.security.auth.module.NTLoginModule Required
debug=true;
};
This basic application specifies that a LoginContext
should use
the NTLoginModule
for authentication. The class name is specified
in the ModuleClass
entry. The Flag
entry controls the
login behavior when multiple modules exist for a single block.
Flag
's four allowable values: Required
,
Sufficient
, Requisite
, and Optional
. The
most common is Required
, which means the module is always called,
and its authentication must pass for the overall authentication to succeed. The
Flag
field's idiosyncrasies can grow complex, and, since we're
using only one module per block in our example, we won't go into elaborate
detail here.
The ModuleOptions
entry allows any number of module-specific
variables to be specified in name-value pairs. For example, many prebuilt login
modules let you specify a debug flag such as debug=true
to see
diagnostic output sent to System.out
.
The configuration file can have any name and location. A running JAAS
framework locates it using the java.security.auth.login.config
system property. Running our sample application, JaasTest
, with a
configuration file called jaas.config
, we would specify the
location on the command line as follows: java
-Djava.security.auth.login.config=jaas.config JaasTest
.
Figure 2 demonstrates the relationship among these configuration elements.
Figure 2. JAAS configuration |
Login: Command line
To demonstrate
what you can do with JAAS, we'll walk you through the development of two
examples: a simple, traditional command-line program and a more complex
server-side JSP application. Both will query the user for a username/password
combination and authenticate using a RDBMS via JDBC.
To authenticate against the database, we:
RdbmsLoginModule
that can authenticate against the
data
LoginContext
how to
use RdbmsLoginModule
ConsoleCallbackHandler
to gather user input
RdbmsLoginModule
, the
configuration file, and the ConsoleCallbackHandler
In our RdbmsLoginModule
, we must implement the five methods
defined in the LoginModule
interface. The first of these is the
initialize()
method, which we define as follows:
public void initialize(Subject subject, CallbackHandler
callbackHandler,
Map
sharedState, Map options)
{
this.subject =
subject;
this.callbackHandler =
callbackHandler;
this.sharedState =
sharedState;
this.options =
options;
url
= (String)options.get("url");
driverClass =
(String)options.get("driver");
debug =
"true".equalsIgnoreCase((String)options.get("debug"));
}
LoginContext
calls this method at the beginning of every call to
its login()
method. As with typical LoginModule
s,
RdbmsLoginModule
's first task is to keep a reference of the four
arguments it has been given. The Subject
, which was either supplied
at the application layer or created by the LoginContext
, can later
be populated with Principal
s and credentials if we successfully
authenticated. The CallbackHandler
will be used in our
login()
method. The sharedState
Map
can
share data across LoginModules
; a nice feature, but we won't use it
in this example.
Last, but certainly not least, is the Map
of named
options
. The options Map
is the way
LoginModule
receives the option values from its corresponding
ModuleOptions
field in the configuration file. When the
LoginContext
loads the configuration, it creates a Map
from these name-value pairs and passes them through an options
parameter.
Our example's configuration file looks like this:
Example
{
RdbmsLoginModule
required
driver="org.gjt.mm.mysql.Driver"
url="jdbc:mysql://localhost/jaasdb?user=root"
debug="true";
};
As you can see, the RdbmsLoginModule
takes three options;
driver
and url
are required, and debug
is
optional. The first two options tell us how to make our JDBC connection: through
a standard JDBC driver class name and a JDBC URL. A more advanced implementation
might use additional option entries to specify a portion of the actual database
query, such as the table or column names. The main idea is that you can use the
configuration file to parameterize the JDBC access. Our
LoginModule
's initialize()
method keeps a copy of each
option.
Earlier, we mentioned that a LoginContext
's configuration file
tells it which application entry to use. That information passes through the
LoginContext
's constructor. Here's the initial
application/client-layer code that creates and invokes LoginContext
(we will explain the CallbackHandler
shortly):
ConsoleCallbackHandler cbh = new
ConsoleCallbackHandler();
LoginContext lc = new
LoginContext("Example", cbh);
lc.login();
Now when the LoginContext.login()
method is called, it will
invoke the login()
method on all LoginModule
s it has
loaded, which for this configuration is only our RdbmsLoginModule
.
Our login()
method then performs the following steps:
Callback
s that will receive username/password
information from the user. It uses two of the built-in JAAS callbacks from the
javax.security.auth.callback
package: NameCallback
and PasswordCallback
. Both of these classes implement the
Callback
interface, a marker interface with no methods.
Callback
s indirectly by passing them to the
handle()
method of the CallbackHandler
parameter
specified in the call to initialize()
.
Callback
s.
rdbmsValidate()
method,
which checks them against the database via a JDBC query. This sequence is shown below in this snippet from
RdbmsLoginModule
's login()
method:
public boolean login() throws LoginException {
if
(callbackHandler ==
null)
throw new
LoginException("no handler");
NameCallback nameCb = new
NameCallback("user: ");
PasswordCallback passCb = new
PasswordCallback("password: ", true);
callbacks = new Callback[]
{ nameCb, passCb };
callbackHandler.handle(callbacks);
String username =
nameCb.getName();
String password = new
String(passCb.getPassword());
success = rdbmsValidate(username,
password);
return(true);
}
Our rdbmsValidate()
method, if successful, assigns any
Principal
s or credentials necessary for our application.
We can see how user interaction works with the Callback
s by
examining the implementation of ConsoleCallbackHandler
's
handle()
method:
public void handle(Callback[]
callbacks)
throws
java.io.IOException, UnsupportedCallbackException
{
for (int i = 0; i < callbacks.length; i++)
{
if (callbacks[i] instanceof
NameCallback)
{
NameCallback
nameCb =
(NameCallback)callbacks[i];
System.out.print(nameCb.getPrompt());
String
user=(new BufferedReader(new
InputStreamReader(System.in))).readLine();
nameCb.setName(user);
}
else if (callbacks[i] instanceof PasswordCallback)
{
PasswordCallback
passCb =
(PasswordCallback)callbacks[i];
System.out.print(passCb.getPrompt());
String
pass=(new BufferedReader(new
InputStreamReader(System.in))).readLine();
passCb.setPassword(pass.toCharArray());
}
else
{
throw(new
UnsupportedCallbackException(callbacks[i],
"Callback
class not
supported"));
}
}
}
Login: Use JAAS plus JSP plus RDBMS
Now that we have our command-line version working, we want
to reuse it in a Web application. You might be asking yourself, "Well, how the
heck am I supposed to launch a command prompt for a Web user to login?"
And you're right—that login approach won't work. Because of the differences
between traditional JAAS callback-based authentication and stateless HTTP, we
can't use any of the provided standard Callback
s or
CallbackHandler
s; they just don't make sense for connectionless
HTTP, and you can't very well open up a command prompt for user input.
You might also be thinking that we could instead use HTTP basic
authentication, whereby we could get the input through the browser's pop-up
Username/Password dialog box. But that has many complications; it requires
multiple round-trip HTTP connections (hard to do from our single
login()
method) and is used less often than form-based Web logins.
We'll follow the form-based Web approach in this article's example: we'll get
the information from an HTML form and process it via our
RdbmsLoginModule
.
Given that we don't interact with our LoginModule
directly from
the application layer, only indirectly through the LoginContext
,
how we will get our username and password from the form into our
LoginModule
for authentication is not immediately apparent.
Alternatives are available. One option: Prior to creating the
LoginContext
, we could instantiate a Subject
and store
the username and password in that Subject
's credentials. Then we
could supply this prepopulated Subject
to the
LoginContext
constructor. Although technically that would work,
it's a bit unwieldy as it adds more security-related code at the application
layer, and Subject
s are typically populated after authentication,
not before.
But given, as we showed above, that we can create our own
CallbackHandler
and give that to the LoginContext
, we
can use a similar design here to manage our username/password data. To do that,
we created a new class called PassiveCallbackHandler
. Here's how
we'll use it from our JSP:
String user = request.getParameter("user");
String pass = request.getParameter("pass");
PassiveCallbackHandler cbh = new PassiveCallbackHandler(user,
pass);
LoginContext lc = new LoginContext("Example",
cbh);
Our new CallbackHandler
has a constructor that takes the
username and password so it doesn't actually need to prompt anyone for
information. Then later it simply sets the appropriate values in the
Callback
s so the LoginModule
can remove them as it did
in the command-line example. Here's how the
PassiveCallbackHandler
's handle()
method works:
public void handle(Callback[] callbacks)
throws
java.io.IOException, UnsupportedCallbackException
{
for (int i = 0; i <
callbacks.length; i++)
{
if
(callbacks[i] instanceof NameCallback)
{
NameCallback
nameCb = (NameCallback)callbacks[i];
nameCb.setName(user);
}
else if(callbacks[i] instanceof PasswordCallback)
{
PasswordCallback
passCb =
(PasswordCallback)callbacks[i];
passCb.setPassword(pass.toCharArray());
}
else
{
throw(new
UnsupportedCallbackException(callbacks[i],
"Callback
class not
supported"));
}
}
}
}
We only removed a line or two from the
ConsoleCallbackHandler
—the lines that prompted for user input.
Thus, we decided to call our implementation passive: because it does
not need to interact with the user.
A note about using this example from JSP: We need to set the system property
so the LoginContext
knows where to look for the
"Example"
login configuration. In this example, we use the Resin
application server. With its configuration setup, we edit the
resin.conf
file, and add a node under the
<caucho.com>
node that looks like this:
<system-property
java.security.auth.login.config="/resin/conf/jaas.config"/>
Here are some additional ideas for JSP-based JAAS:
CallbackHandler
with an
HttpServletRequest
argument, you can authenticate using
additional information from the request, including the client's IP address.
LoginContext
also implement the
HttpSessionBindingListener
interface, you can log users out when
their sessions end (see example class SessionLoginContext
).
Other notes
We detailed the
authentication half of JAAS only.
Note that you can use server-side JAAS as the basis for a single sign-on framework, whereby a user is authenticated once and is subsequently granted appropriate access to multiple protected systems and services. Such a framework might use JAAS for authentication and JNDI (Java Naming and Directory Interface) for accessing secured services.
Develop more secure apps with JAAS
By providing a dynamic and extensible model for
authenticating users and controlling permissions, JAAS gives you the tools to
add more robust security to your applications. The flexibility of creating your
own login mechanisms, while simultaneously leveraging prebuilt modules and a
consistent framework, is a big plus.
JAAS works both on client-side and server-side applications. Though the state of affairs for JAAS on the server is certainly in flux right now, it will certainly stabilize going forward. As long as you're aware of what JAAS is and how it works, you'll be in a good position to leverage its capabilities however it evolves.
About the author
John Musser is a
consultant and writer who, over the past 20 years, has built software ranging
from global Wall Street trading systems to games for Electronic Arts. He is
currently lead architect on a large-scale J2EE logistics system and teaches
software development at Columbia University.
Paul Feuer has
developed large-scale intranet and extranet commercial Web applications for the
past five years. He is currently a lead software engineer for a hybrid
peer-to-peer document collaboration system and a graduate student in the
Computer Science Department at New York University.
The Java security puzzle
In addition to JAAS, the ever-growing Java security framework now includes four additional key elements. The first two include extensions developed to support security standards like PKI (public key infrastructure) and SSL (Secure Socket Layer): the Java Cryptography Extension (JCE) and the Java Secure Socket Extension (JSSE). These were introduced as Java 2 extensions and are now part of J2SE 1.4. The two most recent additions are the Generic Security Services API
(GSS-API) and Java Certification Path API. The GSS-API provides
standardized interfaces for identity, confidentiality, and data integrity
on a per-message basis, such as through Kerberos 5. The Java Certification
Path API adds to the |