Oracle GlassFish Server 3.0.1 Application Development Guide

Adding Authentication Mechanisms to the Servlet Container

You can use JSR 196 in the web tier to facilitate the injection of pluggable authentication modules within the servlet constraint processing engine. The GlassFish 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 GlassFish Server and JSR 196

The GlassFish 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 JSR 196 specification defines a simple message processing model composed of four interaction points:

  1. secureRequest on the client

  2. validateRequest on the server

  3. secureResponse on the server

  4. 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 GlassFish 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.

Writing a Server Authentication Module

A key step in adding an authentication mechanism to a compatible server-side message processing runtime such as the GlassFish 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:

See the Servlet Container Profile section in the JSR 196 specification for additional background and details.

Sample Server Authentication Module

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.

Compiling and Installing a Server Authentication Module

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 GlassFish 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.

Configuring a Server Authentication Module

You can configure a SAM in one of these ways:

Binding a Server Authentication Module 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 two options in how you bind the SAM, depending on whether you are willing to repackage and redeploy your application: