Wednesday Apr 30, 2008
Wednesday Apr 30, 2008
by Ron Monzillo
This tip will show you how to implement and configure new authentication mechanisms in the GlassFish v2 servlet container. GlassFish v2 includes implementations of a number of HTTP layer authentication mechanisms such as Basic, Form, and Digest authentication. You can use the techniques described in this tip to add alternative implementations of the included mechanisms or to add implementations of new mechanisms such as HTTP Negotiate/SPNEGO, OpenID, or CAS.
GlassFish v2 and JSR 196
GlassFish v2 implements the Servlet Container Profile
of JRS-196, Java Authentication Service Provider Interface
for Containers. JSR 196 defines a standard service-provider interface (SPI) for integrating authentication mechanism
implementations in message processing runtimes. You can use the techniques covered in this tip to add authentication
mechanisms to GlassFish because GlassFish supports the JSR 196 standard. JSR 196 extends the concepts of the
Java Authentication and Authorization Service (JAAS) to enable pluggability of message authentication modules in message
processing runtimes. The standard defines profiles that establish contracts for the use of the SPI in specific contexts.
The Servlet Profile of JSR-196 defines the use of the SPI by a Servlet container such that (1) the resulting container can
be configured with new authentication mechanisms, and (2) the container employs the configured mechanisms in its
enforcement of the declarative servlet security model (declared in a web.xml file using security-constraint
elements).
The JSR 196 specification defines a simple message processing model composed of four interaction points, 2 on the client side, and 2 on the server side, as shown in Figure 1.
|
|
Figure 1. Security Processing Injection Points |
A message processing runtime uses the SPI at these interaction points to delegate the corresponding message security processing to authentication providers, also called authentication modules, integrated into the runtime by way of the SPI.
A compatible server-side message processing runtime, such as the GlassFish servlet container, supports the
validateRequest and secureResponse interaction points of the message processing model.
The servlet container uses the SPI at these interaction points to delegate the corresponding message security
processing to a server authentication module (SAM), integrated by the SPI into the container.
A key step in adding an authentication mechanism to a compatible server-side message processing runtime such as the GlassFish servlet container, is acquiring a SAM that implements the desired authentication mechanism. One way to do that is to write the SAM yourself, so let's examine how to do that.
Writing a SAM
A SAM implements the javax.security.auth.message.module.ServerAuthModule interface as defined by JSR 196
and is invoked (indirectly) by the message processing runtime at the validateRequest and
secureResponse interaction points. A SAM must implement the five methods of the ServerAuthModule
interface, each of which is described briefly below. See section "3. Servlet Container Profile" in the
JSR 196 specification for additional background and details.
getSupportedMessageTypes()Class objects where each element defines a message type supported by the SAM.
For a SAM to be compatible with the Servlet Container profile, the returned array must include the
HttpServletRequest.class and HttpServletResponse.class objects.
initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler Map options)CallbackHandler.
The configuration values are returned in the policy arguments and in the options Map. The SAM uses
CallbackHandler to access services, such as password validation, provided by the container.
AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject)HttpServletRequest. The request and
its associated HttpServletResponse are passed by the container to the SAM in the messageInfo
argument. The SAM processes the request and may establish the response to be returned by the
container. The SAM uses the provided Subject arguments to convey its authentication results. The
SAM returns different status values to control the container's invocation processing. The status values and the
circumstances under which they are returned are as follows:
AuthStatus.SUCCESS is returned when the application request message is successfully validated.
The container responds to this status value by using the returned client Subject to invoke the target of the
request. When this value is returned, the SAM (provided a custom AuthConfigProvider is not being
used) must use its CallbackHandler to handle a CallerPrincipalCallback using the
clientSubject as an argument to the callback.
AuthStatus.SEND_CONTINUE indicates that message validation is incomplete and that
the SAM has established a preliminary response as the response message in messageInfo. The container
responds to this status value by sending the response to the client.
AuthStatus.SEND_FAILURE indicates that message validation failed and that the SAM has established
an appropriate failure response message in messageInfo. The container responds to this status
value by sending the response to the client.
AuthStatus.SEND_SUCCESS is not typically returned. This status value indicates the end of
a multi-message security dialog originating after the service interaction and during the processing of the application
response. The container responds to this status value by sending the response to the client.
ValidateRequest may also throw an AuthException to indicate that the message processing
by the SAM failed without establishing a failure response message in messageInfo.
secureResponse(MessageInfo messageInfo, Subject serviceSubject)messageInfo argument. In
most cases, this method should just return the SEND_SUCCESS status.
Sample SAM
The class MySam.java is a sample SAM implementation. Notice that the sample
implements the five methods of the ServerAuthModule interface. For example, here is
the implementation of the initialize() method:
public void initialize(MessagePolicy reqPolicy,
MessagePolicy resPolicy,
CallbackHandler cBH, Map opts)
throws AuthException {
requestPolicy = reqPolicy;
responsePolicy = resPolicy;
handler = cBH;
options = opts;
}
Also notice that the validateRequest method is stubbed. For authorization-protected resources,
validateRequest returns a "FORBIDDEN" status code along with the message "authentication
required and not yet implemented." However, validateRequest allows access for unprotected resources
by returning AuthStatus.SUCCESS. The SAM uses the return value of requestPolicy.isMandatory()
to determine when the request is to an authorization-protected resource. The servlet container profile mandates that
the message processing runtime establish the requestPolicy such it can be used by the SAM to determine
when authentication is required.
public AuthStatus validateRequest(
MessageInfo msgInfo, Subject client,
Subject server) throws AuthException {
try {
if (requestPolicy.isMandatory()) {
HttpServletResponse response =
(HttpServletResponse)
msgInfo.getRequestMessage();
response.setStatus
(HttpServletResponse.SC_FORBIDDEN);
response.sendError(
HttpServletResponse.SC_FORBIDDEN,
"authentication required and
not yet implemented");
return AuthStatus.SEND_FAILURE;
} else {
setAuthenticationResult(null,
client, msgInfo);
return AuthStatus.SUCCESS;
}
} catch (Exception e) {
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
}
}
Before you can use the sample SAM, you need to compile, install, and configure it. Then you can bind it to an application.
Compiling and Installing the SAM
To compile the SAM, you need to include the SPI in your classpath. When GlassFish is installed, the JAR file containing the
SPI, jmac-api.jar, is installed in the lib subdirectory under the root of your GlassFish installation
directory.
After you compile the SAM, install it by copying a JAR file containing the compiled SAM to the lib
subdirectory under the root of the GlassFish installation directory.
Configuring the SAM
You can configure a SAM for GlassFish v2 using the GlassFish v2 Admin Console, officially known as the Sun Java System Application Server Admin Console. Here are the steps:
<GF_HOME>bin/asadmin start-domain domain1
where <GF_HOME> is the directory where you installed GlassFish v2.
http://localhost:4848/.If you need to create the layer, you will need to configure the provider. To do that:
Provider Type: server
Provider ID: MySam
Class Name: SAM, that is, tip.sam.MySam.
Do not check the Default Provider: Enabled check box.
|
|
Figure 2. Configuring the Provider for the SAM |
If the HttpServlet layer already exists, do the following:
Provider Type: server
Provider ID: MySam
Class Name: SAM, that is, tip.sam.MySam.
Do not check the Default Provider: Enabled check box.
Note that the provider configuration utility also provides a dialog box that you can use to configure additional
properties. If you configure additional properties in this way, the properties are passed
in the options parameter when the SAM’s initialize method is called. The sample SAM in this tip
does not require any initialization properties. However, another SAM, for example, could support a property naming
the group principals to add as a side effect of a successful authentication.
At this point the SAM is installed and available for use by GlassFish v2.
Binding the SAM to Your Application
After you install and configure the SAM, you can bind it for use by the container on behalf of one or more of your applications. You have a few options in how you bind the SAM, depending on whether or not you are willing to repackage and redeploy your application.
Option1: If you are willing to repackage and redeploy, you can bind the SAM within the sun-web.xml
file of your web application. You do this by configuring the httpservlet-security-provider attribute
of the sun-web-app element to contain the provider id assigned when the SAM was installed, that is,
MySam.
If you use the NetBeans IDE, open the configuration file (in the non-XML view) and select the General tab. Assign the provider id to the field titled Http Servlet Security Provider. You then need to rebuild and redeploy the application.
Option 2: The first option leverages the native AuthConfigProvider implementation that ships with GlassFish. Another approach would be to develop your own AuthConfigProvider and register it with the Glassfish AuthConfigFactory for use on behalf of your applications. For example, a simple AuthConfigProvider might obtain, through its initialization properties, the classname of a SAM to configure on behalf of the applications for which the provider is registered. You can find a description of the functionality of an AuthConfigProvider and of the registration facilities provided by an AuthConfigFactory in the JRS-196 specification.
After you have bound the SAM for use by the container on behalf of your application(s), the container will invoke
MySam whenever a request is made to a resource within your application. Moreover, when a request is made
to a resource within your application that is protected by an auth-constraint, MySam will not dispatch
the request until it has determined that a successful authentication has occurred.
Let's complete the implementation of validateRequest so that MySam will be able to perform
the authentication.
Revising the SAM
You've gone through the steps of installing, configuring, and binding a SAM. Now let's return to
the authentication mechanism implementation and make some changes to complete the implementation of the
validateRequest method. To keep things simple, let's change the SAM to implement HTTP Basic
Authentication, or at least an approximation of it.
Here is the modified SAM. The primary changes are as follows:
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.callback.PasswordValidationCallback;
import org.apache.catalina.util.Base64;
private String realmName = null;
private String defaultGroup[] = null;
privte static final String REALM_PROPERTY_NAME =
"realm.name";
private static final String GROUP_PROPERTY_NAME =
"group.name";
private static final String BASIC = "Basic";
static final String AUTHORIZATION_HEADER =
"authorization";
static final String AUTHENTICATION_HEADER =
"WWW-Authenticate";
initialize method looks for the group.name and realm.name properties.
The group.name property is used to configure the default group that will be assigned as a result of any
successful authentication. The realm.name property is used to define the realm value sent back to the
browser in the www-authenticate challenge.
public void initialize(MessagePolicy reqPolicy,
MessagePolicy resPolicy,
CallbackHandler cBH, Map opts)
throws AuthException {
requestPolicy = reqPolicy;
responsePolicy = resPolicy;
handler = cBH;
options = opts;
if (options != null) {
realmName = (String)
options.get(REALM_PROPERTY_NAME);
if (options.containsKey(GROUP_PROPERTY_NAME)) {
defaultGroup = new String[]{(String)
options.get(GROUP_PROPERTY_NAME)};
}
}
}
validateRequest method is revised to evaluate the www-authorize header and
to issue the www-authenticate challenge.
public AuthStatus validateRequest(
MessageInfo msgInfo, Subject client,
Subject server) throws AuthException {
try {
String username =
processAuthorizationToken(msgInfo, client);
if (username ==
null && requestPolicy.isMandatory()) {
return sendAuthenticateChallenge(msgInfo);
}
setAuthenticationResult(
username, client, msgInfo);
return AuthStatus.SUCCESS;
} catch (Exception e) {
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
}
}
setAuthenticationResult method is extended to add the defaultGroup if it is configured.
// distinguish the caller principal
// and assign default groups
private void setAuthenticationResult(String name,
Subject s, MessageInfo m)
throws IOException,
UnsupportedCallbackException {
handler.handle(new Callback[]{
new CallerPrincipalCallback(s, name)
});
if (name != null) {
// add the default group if the property is set
if (defaultGroup != null) {
handler.handle(new Callback[]{
new GroupPrincipalCallback(s, defaultGroup)
});
}
m.getMap().put(AUTH_TYPE_INFO_KEY, ""MySAM");
}
}
Configuring the Revised SAM
After you have built and installed the revised SAM in the GlassFish lib directory, from your
browser, use the GlassFish v2 Admin Console to reconfigure the SAM as follows:
group.name in the Name field and user in the Value field.realm.name in the Name field and Sam in the Value field.Alternatively, you could create a second provider with a different name and with the values for the defined properties. You could then switch the binding of your application to the new provider and redeploy your application.
As before, after the revised SAM is installed and bound to your application, it will be invoked by the container
on behalf of your application. When you attempt to access an auth-protected resource within your application,
the revised SAM will prompt you for a username and password. The SAM will use the CallbackHandler
to ask the container to validate the supplied username and password using the realm configured for your application.
For the validation to succeed, you will need to know the username and password of a user in the realm configured
for your application.
By default, GlassFish applications are configured to use the file realm. To add a user to the file realm, use the GlassFish v2 Admin Console as follows:
Further Reading
You can learn more about using JSR 196 in the following documents:
About the Author
Ron Monzillo is the specification lead for JSR 196 and JSR 115: Java Authorization Contract for Containers. He defined the declarative security model for servlets and led the team that defined the EJB Secure Interoperability protocol, that is, CORBA/CSIv2. He also made contributions to the OASIS WS-Security standards and was the primary author of the WS-Security SAML token profile. Ron participates in most security activities relating to Java EE, servlets, and GlassFish.
2008 JavaOne Conference: Last Chance for Discount Registration
Don't miss this opportunity. Register by May 5, and save $100 for the JavaOne conference, May 6-9, 2008, in San Francisco. Experience 300+ sessions on Java technology, Rich Internet Applications, open source, compatibility and interoperability, next-generation scripting languages, Web 2.0, services integration, e-commerce collaboration, and more. Meet with 100+ companies leading Java technology innovations and expanding into new technologies; and take advantage of opportunities to meet the experts in the SOA Village, Mobility and Device Village, and Startup Exhibitor Alley, all in the JavaOne Pavilion.
Use priority code J8EM5IC, and register today at java.sun.com/javaone.