You can use JSR 196 in the web tier to facilitate the injection of pluggable authentication modules within the servlet constraint processing engine. The Enterprise Server includes implementations of a number of HTTP layer authentication mechanisms such as basic, form, and digest authentication. You can add alternative implementations of the included mechanisms or implementations of new mechanisms such as HTTP Negotiate/SPNEGO, OpenID, or CAS. JSR 196 server authentication modules are described in the following sections:
The Enterprise Server implements the Servlet Container Profile of JSR 196, Java Authentication Service Provider Interface for Containers. JSR 196 defines a standard service provider interface (SPI) that extends the concepts of the Java Authentication and Authorization Service (JAAS) to enable pluggability of message authentication modules in message processing runtimes. The JSR 196 standard defines profiles that establish contracts for the use of the SPI in specific contexts. The Servlet Container Profile of JSR 196 defines the use of the SPI by a Servlet container such that:
The resulting container can be configured with new authentication mechanisms.
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:
secureRequest on the client
validateRequest on the server
secureResponse on the server
validateResponse on the client
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 Enterprise Server 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 Enterprise Server servlet container is acquiring a SAM that implements the desired authentication mechanism. One way to do that is to write the SAM yourself.
A SAM implements the javax.security.auth.message.module.ServerAuthModule interface as defined by JSR 196. A SAM 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:
getSupportedMessageTypes() — An array of 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) — The container calls this method to provide the SAM with configuration values and with a 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) — The container calls this method to process each received 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.
The validateRequest method 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) — The container calls this method before sending a response, resulting from an application invocation, to the client. The response is passed to the SAM in the messageInfo argument. In most cases, this method should just return the SEND_SUCCESS status.
cleanSubject(MessageInfo messageInfo, Subject subject) — This method removes the mechanism-specific principals, credentials, or both from the subject. This method is not currently called by the container. A legitimate implementation could remove all the principals from the argument subject.
See the Servlet Container Profile section in the JSR 196 specification for additional background and details.
The class MySam.java is a sample SAM implementation. Notice that the sample implements the five methods of the ServerAuthModule interface. This SAM implements an approximation of HTTP basic authentication.
package tip.sam; import java.io.IOException; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.message.AuthException; import javax.security.auth.message.AuthStatus; import javax.security.auth.message.MessageInfo; import javax.security.auth.message.MessagePolicy; import javax.security.auth.message.callback.CallerPrincipalCallback; import javax.security.auth.message.callback.GroupPrincipalCallback; import javax.security.auth.message.callback.PasswordValidationCallback; import javax.security.auth.message.module.ServerAuthModule; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.util.Base64; public class MySam implements ServerAuthModule { protected static final Class[] supportedMessageTypes = new Class[]{ HttpServletRequest.class, HttpServletResponse.class }; private MessagePolicy requestPolicy; private MessagePolicy responsePolicy; private CallbackHandler handler; private Map options; 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"; 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)}; } } } public Class[] getSupportedMessageTypes() { return supportedMessageTypes; } 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; } } private String processAuthorizationToken( MessageInfo msgInfo, Subject s) throws AuthException { HttpServletRequest request = (HttpServletRequest) msgInfo.getRequestMessage(); String token = request.getHeader(AUTHORIZATION_HEADER); if (token != null && token.startsWith(BASIC + " ")) { token = token.substring(6).trim(); // Decode and parse the authorization token String decoded = new String(Base64.decode(token.getBytes())); int colon = decoded.indexOf(':'); if (colon <= 0 || colon == decoded.length() - 1) { return (null); } String username = decoded.substring(0, colon); // use the callback to ask the container to // validate the password PasswordValidationCallback pVC = new PasswordValidationCallback(s, username, decoded.substring(colon + 1).toCharArray()); try { handler.handle(new Callback[]{pVC}); pVC.clearPassword(); } catch (Exception e) { AuthException ae = new AuthException(); ae.initCause(e); throw ae; } if (pVC.getResult()) { return username; } } return null; } private AuthStatus sendAuthenticateChallenge( MessageInfo msgInfo) { String realm = realmName; // if the realm property is set use it, // otherwise use the name of the server // as the realm name. if (realm == null) { HttpServletRequest request = (HttpServletRequest) msgInfo.getRequestMessage(); realm = request.getServerName(); } HttpServletResponse response = (HttpServletResponse) msgInfo.getResponseMessage(); String header = BASIC + " realm=\"" + realm + "\""; response.setHeader(AUTHENTICATION_HEADER, header); response.setStatus( HttpServletResponse.SC_UNAUTHORIZED); return AuthStatus.SEND_CONTINUE; } public AuthStatus secureResponse( MessageInfo msgInfo, Subject service) throws AuthException { return AuthStatus.SEND_SUCCESS; } public void cleanSubject(MessageInfo msgInfo, Subject subject) throws AuthException { if (subject != null) { subject.getPrincipals().clear(); } } private static final String AUTH_TYPE_INFO_KEY = "javax.servlet.http.authType"; // 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"); } } }
Note that the initialize method looks for the group.name and realm.name properties. The group.name property configures the default group assigned as a result of any successful authentication. The realm.name property defines the realm value sent back to the browser in the WWW-Authenticate challenge.
Before you can use the sample SAM, you need to compile, install, and configure it. Then you can bind it to an application.
To compile the SAM, include the SPI in your classpath. When the Enterprise Server is installed, the JAR file containing the SPI, jmac-api.jar, is installed in the as-install/lib directory. After you compile the SAM, install it by copying a JAR file containing the compiled SAM to the as-install/lib directory.
You can configure a SAM in one of these ways:
In the Administration Console, open the Security component under the relevant configuration and go to the Message Security page. Set the following options:
Authentication Layer — HttpServlet
Provider Type — server or client-server
Provider ID — Specify a unique name for the SAM, for example MySAM
Class Name — Specify the fully qualified class name, for example tip.sam.MySam
Additional Property — Name: group-name Value: user
Additional Property — Name: realm-name Value: Sam
For details, click the Help button in the Administration Console.
Use the asadmin create-message-security-provider command to configure a SAM. Set the following options:
--layer HttpServlet
--providertype server or --providertype client-server
--classname tip.sam.MySam
--property group-name=user:realm-name=Sam
Provider name operand — Specify a unique name for the SAM, for example MySAM
For details, see the Sun GlassFish Enterprise Server v3 Reference Manual.
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 two options in how you bind the SAM, depending on whether you are willing to repackage and redeploy your application:
If you are willing to repackage and redeploy, you can bind the SAM using the sun-web.xml file. Set the value of the httpservlet-security-provider attribute of the sun-web-app element to the SAM's configured provider ID, for example, MySAM. For more information about the sun-web.xml file, see the Sun GlassFish Enterprise Server v3 Application Deployment Guide. This option leverages the native AuthConfigProvider implementation that ships with the Enterprise Server.
Another approach is to develop your own AuthConfigProvider and register it with the Enterprise Server AuthConfigFactory for use on behalf of your applications. For example, a simple AuthConfigProvider can 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 JSR 196 specification.