Adding Authentication Mechanisms to the GlassFish Servlet Container

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.

Security Processsing Injection Points

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()
    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.

    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)
    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 and/or credentials from the subject. This method is not currently called by the container. A legitimate implementation could remove all the principals from the argument subject.

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.getResponseMessage();
                   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:

  1. Make sure the GlassFish V2 Application Server is running. If it is not already running, you can start it using the following command:
       <GF_HOME>bin/asadmin start-domain domain1
    

    where <GF_HOME> is the directory where you installed GlassFish v2.

  2. Open the GlassFish v2 Admin Console by pointing your browser to the URL: http://localhost:4848/.
  3. Login to the Admin Console by entering your ID and password.
  4. Expand the Configuration node at the bottom of the left-hand pane.
  5. Navigate to the Security node, expand it, and click MessageSecurity.
  6. Under Message Security Configurations, either open the HttpServlet layer if it already exists, or create it if it doesn't exist by clicking the New button. Clicking the button opens the New Message Security Configuration window.
  7. If you need to create the layer, you will need to configure the provider. To do that:

    • Set the following in the New Message Security Configuration window as shown in Figure 2:

      Provider Type: server
      Provider ID: MySam
      Class Name: SAM, that is, tip.sam.MySam.

      Do not check the Default Provider: Enabled check box.

    • Configuring the Provider for the SAM

      Figure 2. Configuring the Provider for the SAM

    • Click the OK button. This saves the settings and opens the Message Security Configurations window.
    • Click on HttpServlet in the Authentication Layer column.
    • Select the Providers tab. This opens the Provider Configuration window.
    • Select MySam in the Provider ID column. This opens the Edit Provider Configuration window.
    • Click the Save button to complete the configuration of the provider.

    If the HttpServlet layer already exists, do the following:

    • Open the HttpServlet layer by selecting it in the Message Security Configurations window.
    • Select the Providers tab to open the Provider Configuration window.
    • Click the New button to open the New Provider Configuration window.
    • In the Provider Configuration area of the window, set 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:

  • New imports are added to account for the use of the Group and Password Validation callbacks, and the Base64 decoder.
        import javax.security.auth.message.callback.GroupPrincipalCallback;
        import javax.security.auth.message.callback.PasswordValidationCallback;
        import org.apache.catalina.util.Base64;
    
  • New constants are added to represent the property names and the HTTP Basic header identifiers.
        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";
    
  • The 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)};
               }
           }
       }
    
  • The implementation of the 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;
           }
       }
    
  • The 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:

  1. Expand the Configuration node at the bottom of the left-hand pane.
  2. Navigate to the Security node, expand it, and click Message Security.
  3. Under Message Security Configurations, select HttpServlet.
  4. Select the Providers tab.
  5. Click the entry for MySam to open the Edit Provider Configuration screen.
  6. Click the Add Property button in the Additional Properties frame and enter group.name in the Name field and user in the Value field.
  7. Click the Add Property button again and enter realm.name in the Name field and Sam in the Value field.
  8. Click the Save button.

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:

  1. Expand the Configuration node at the bottom of the left-hand pane.
  2. Navigate to the Security node, expand it, and click on Realms.
  3. Under Realms, open the file realm.
  4. Click the Manage Users button.
  5. Click on New.
  6. Fill in values for "User Id", "New Password", and "Confirm New Password".
  7. Click the OK button.

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.

Comments:

Wow, to achieve this seemingly standard infrastructure functionality so much work?
Anybody learned from j2ee 1-1.4?

Consider: compare efforts to do this programmatically, and have complete freedom - and also have a principal object available right on - to this, learing it, configure it, dealing with appserver updates etc.

Posted by Ben on October 18, 2008 at 05:56 AM PDT #

I think you are over estimating the work inherent in the 196 approach, while underestimating that of a programmatic approach. In my experience, they are of at least the same order, the added benefit of the JSR 196 based approach being that it allows new network authentication mechanisms to be integrated in the security constraint processing machinery of the web container; especially such that enforcement of access control policy remains in the container, and thus outside of the application. This remains the core value proposition of the Java EE security model. Conversely, programmatic, application layer techniques, perform authentication after the container access checks, in effect become embedded in the application, and typically must be accompanied by a similarly embedded access control mechanism. I accept that there are cases where application layer mechanisms will be preferred, but as I tried to convey above, the JSR 196 methodology serves to further empower the Java EE security model, where security policy is enforced by the container on behalf of applications.

Posted by Ron Monzillo on October 20, 2008 at 05:36 AM PDT #

Does this work with GlassFish v3? There does not seem to be a message security configuration panel in the prelude version out currently. Should one look somewhere else?

Posted by Henry Story on February 18, 2009 at 07:05 AM PST #

Yes, V3 prelude implements the Servlet Profile of JSR 196, but its admin-console not provide screens to create the provider config in domain.xml (corresponding to the GFAuthConfigProvider).

The admin support will be be restored in V3 (after prelude), but in prelude you must use an editor to add the corresponding configuration entries to domain.xml. A sample config entry would appear (in domain.xml) as follows:

<message-security-config auth-layer="HttpServlet">
<provider-config class-name="tip.MySam" provider-id="MySam" provider-type="server">
<request-policy/>
<response-policy/>
</provider-config>
</message-security-config>

You also have the option of implementing and registering another AuthConfigProvider, which would allow you to take control of the configuration interfaces and mechanism. I'll add a sample AuthConfigProvider implementation under the OpenSSO project at
https://opensso.dev.java.net/source/browse/opensso/extensions/jsr196

Posted by Ron Monzillo on February 23, 2009 at 12:40 AM PST #

I don't see how this could sole my Problem with
PWC1404: Servlet of class {} is privileged and cannot be loaded by this web application

Posted by whenhou on March 04, 2009 at 04:18 PM PST #

You wrote, "I don't see how this could sole my Problem with
PWC1404: Servlet of class {} is privileged and cannot be loaded by this web application"

Please provide some more details as to the context in which you are getting the above error. Are you getting this error, while trying to follow the directions in the tip? Please provide some more details as to the context in which you are getting the above error.

Posted by Ron Monzillo on March 05, 2009 at 01:21 AM PST #

In the first example code for MySam.java and the text of this article you have:

HttpServletResponse response =
(HttpServletResponse)
msgInfo.getRequestMessage();

Which throws a ClassCastException.

It should be:

HttpServletResponse response =
(HttpServletResponse)
msgInfo.getResponseMessage();

Just FYI.

Posted by Eric on May 15, 2009 at 12:17 AM PDT #

Eric: You are correct. We'll fix this as soon as possible.

Ed Ort
Sun Developer Connection

Posted by Edward Ort on May 15, 2009 at 06:14 AM PDT #

What does the setting Provider Type "server" in figure 2 mean ? Does it mean that only 2) validateRequest and 3) secureResponse of figure 1 are executed ?

Posted by Jane on July 21, 2009 at 10:21 PM PDT #

yes, it means that the provider only implements the ServerAuthModule interface (which includes validateRequest and secureResponse).

The servlet runtime, only provides a server-side jsr 196 interception point, thus it only invokes the AuthModule via validateRequest and secureResponse.

Posted by Ron Monzillo on July 22, 2009 at 02:15 AM PDT #

Just wondering why there is a SOAPRequestHandler.secureRequest in the logfile. I thought it is just that: the request will be secured by inserting the security header before send to the WSP. Am I wrong ?

Posted by Jane on July 22, 2009 at 05:35 AM PDT #

hello, i would like to use this sample SAM, but with username included in REMOTE_USER http header, and LDAP realm (glassfish),please have any hints for this configuration? problem is that i have not the password. only username which i want to validate against ldap realm, and assign ldap groups to this user.

ps: in websphere there is possibility to write TAI interceptor which doesn't need password, and validate username against user registry..

thanks,

jozef

Posted by jc on August 27, 2009 at 12:10 AM PDT #

I think you can do what you want to do, but that it will involve creating a custom variant of the LDAPRealm that ships with Glassfish, such that it's findAndBind method no longer calls bindAsUser, but instead performs its groupSearch and dynamicGroupSearch as the user it currently uses to do its UserSearch. You would then configure your app to use the custom LDAPRealm, and you could invoke it from the SAM using the PasswordValidationCallback (with the username taken form the header, and with a dummy password value). As a SAM only alternative, you could move the revised findAndBind funtionality into the SAM, and allow the SAM to directly query the LDAP database for the user's group memberships, which the SAM would assign to via the GroupPrincipalCallback.

I have not tried either of these things, and I noticed that the LDAPRealm is final, which makes customization less convenient, but if you want to give either of these ideas a try I'll try to guide you through it as best I can.

Posted by Ronald Monzillo on August 28, 2009 at 07:37 AM PDT #

Thanks a lot for the guide!

As soon as I add the jar into the /lib/ dir, I receive a big list of javax.management.InstanceNotFoundException errors. I can c/p it here but the list is quite big. Am I doing something wrong?

BTW, a couple of minor spelling errors in the revised SAM (lines 40 and 202)

Posted by Gioranus on September 08, 2009 at 01:34 AM PDT #

The best way to convey the exceptions you are seeing, would likely be to create an issue in the Glassfish issue tracker, and attach your log file to it. Remember to assign the problem to the security subcomponent, and to clearly identify the version of Glassfish on which you are seeing a problem.

ps: thanks for the corrections to the tip
40s/privte/private/
202s/ ""MySAM"/ "MySAM"/

Posted by ron monzillo on September 08, 2009 at 07:06 AM PDT #

Could you please also provide the code for digest authentication? Or any clues for doing that?

Posted by chen on December 07, 2009 at 11:16 PM PST #

I should mention that there is some native support in Glassfish for DIGEST auth, in case you might find that sufficent.

Regarding a DIGEST SAM, I haven't written one, but it would be very similar to the BASIC Auth SAM. The details of the challenge and the response are defind in RFC 2617.

Since JSR 196 DOES NOT provide a DIGEST validation callback, you may want to follow the JSR 196 Login Bridge Profile to invoke a JAAS login module to do the DIGEST validation. That would allow you to encapsulate any system specific repository interactions within the login module.

Alternatively, you could do all that stuff directly in your SAM (if you are either not concerned with portability, or intend to crate your own complete implementation).

Posted by Ron Monzillo on December 11, 2009 at 06:42 AM PST #

Post a Comment:
Comments are closed for this entry.
About

edort

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today