Wednesday, November 7, 2012

Implementing container authentication in Java EE with JASPIC

This article takes a look at the state of security support in Java EE 6, with a focus on applications that wish to do their own authentication and the usage of the JASPI/JASPIC/JSR 196 API.

Declarative security is easy

In Java EE it has always been relatively straightforward to specify to which resources security constraints should be applied.

For web resources (Servlets, JSP pages, etc) there is the <security-constraint> element in web.xml, while for EJB beans there's the @RolesAllowed annotation. Via this so called 'declarative security' the programmer can specify that only a user having the given roles is allowed access to the protected web resource, or may invoke methods on the protected bean.

The declarative model has a programmatic counterpart via methods like HttpServletRequest#isUserInRole, where the same kind of role checks can be done from within code (allowing for more elaborate combinations, e.g. has role 'admin' but not has role 'manager', or if price > 5 and not has role 'manager', etc).

This is indeed straightforward and easy to use. Unfortunately, when it comes to implementing the actual authentication code (the code that actually loads a user and associated roles from some place and checks e.g. the password), things are not so simple.

How is authentication traditionally implemented?

Traditionally, Java EE simply didn't say how authentication should be done at all, which greatly confused (new) users. The idea here is that security is setup inside the application server, and is done in a vendor specific way. In addition to that, a WAR or EAR will typically also have to contain vendor specific deployment descriptors which require setting up and configuring vendor specific things, often using vendor specific terminology. For instance, some application servers require specifying something called a "domain", which then approximately but not exactly corresponds to what another server may call a "realm", "zone", or "region". Roles can also rarely just be... roles. Many servers, but not all, require you to first map them to things like a "group", "principal", or "right" (which again are all roughly the same thing).

Ignoring the terminology confusion, this model works well for the situation where externally obtained applications need to be integrated in the existing intranet of an enterprise, and where existing user accounts residing in e.g. the enterprise's LDAP server need to access those applications. Examples of such applications are things like JIRA or Sonar. In that situation, if JIRA would use the role name "admin" and your organization uses the name "administrator", it's convenient that there's a way to map between those roles.

However, for applications that are developed in-house and are solely aimed to be deployed by the same organization that developed them and which are intended for the general Internet public (i.e. your typical web app), all this mandatory mapping is completely unnecessary.

Having the security setup inside the application server is an abstraction that only gets in the way if there is only ever one application deployed to that application server. Worse, because the functionality to create a user account is typically an integrated part of the above mentioned web applications, being forced to setup security outside the application really doesn't work nicely. Among others it prevents the application to easily use its own domain models for the authentication process (like e.g. a JPA entity User). There are some popular workarounds for this, like login modules that allow one to directly query a user and its roles from the same database that the application is using, but these are inelegant at best and require the details about how the User entity is persisted to reside at two places.

Heavyweight

The fact that the authentication mechanism is vendor specific doesn't just hurt the portability of Java EE applications, it also hurts learning about Java EE. Namely, in order to secure a Java EE application, you can't just study Java EE books and tutorials, but you also have to learn e.g. JBoss, or GlassFish. Especially for lesser known application servers, it can be very frustrating to dig up that information. Essentially it makes Java EE developers less able to move between jobs and makes it harder for companies to hire experienced employees.

All of this unfortunately seems to add to the feeling that Java EE is heavyweight, a reputation that Sun, now Oracle and partners have been trying hard to shake off. Indeed, a technology like EJB has been massively slimmed down by among others simply not forcing certain restrictions upon users and having smart defaults. Yes, (business) interfaces for services and separating business code into its own layer may be a best practice in some situations, but it's a choice users should make and in EJB 3.1 this choice was finally given to the user.

What about JAAS?

A common mistake is to think that JAAS (Java Authentication and Authorization Service) is the standardized and portable API that can be used to take care of authentication in Java EE without having to resort to vendor specific APIs.

Unfortunately this is not the case. JAAS comes a long way in introducing a basic set of security primitives and overall establishing a very comprehensive security framework, but one thing it doesn't have knowledge about is how to integrate with a Java EE container. Practically this means that JAAS has no way of communicating a successful authentication to the container. A user may be logged-in to some JAAS module, but Java EE will be totally unaware of this fact. The reverse is also true; when a protected resource is accessed by the user, or when an explicit login is triggered via the Servlet 3 HttpServletRequest#login method, the container has no notion of which JAAS login module should be called. Finally, there is a mismatch between the very general JAAS concept of a so-called Subject having a bag of Principals and the Java EE notion of a caller principal and a collection of roles. For further reading about this particular subject; Raymond Ng wrote an excellent article about this a few years ago.

Nearly all vendor specific authentication mechanisms are in fact based on JAAS, but each vendor has taken its own approach to implementing the container integration, how to map the above mentioned caller principal and roles to the JAAS Subject, and how to let a user install and specify which authentication modules should be used for a given application (or domain, realm, zone, etc).

JASPIC to the rescue... sort off

In actuality, the idea that there should be an API in Java EE that standardized the above mentioned integration already existed a long time ago, in 2002 to be precise when the JASPIC JSR (JSR 196) was created. For some reason or the other, it took a very long time for this JSR to be completed and it wasn't included in Java EE until Java EE 6 (2009).

JASPIC finally standardizes how an authentication module is integrated into a Java EE container. However, it's not without its problems and has a few quirks.

Probably in order to maintain compatibly with the existing ways that containers use the JAAS Subject, JASPIC did not specify which parts of this Subject correspond to the caller principal and roles. Instead, it uses a trick involving a so called callback handler. This works in 2 steps. First JASPIC introduced 2 types (called callbacks), that do contain this information in clearly specified fields. These are called CallerPrincipalCallback and GroupPrincipalCallback. Secondly the authentication module is given a handler implementation that reads the data from those two types and then stores it in a container specific way into the JAAS Subject. It's a bit convoluted, but it does do the trick.

Another strange aspect of JASPIC is its name. Seemingly people can't agree on whether it should be JASPIC or JASPI. Important vendors like JBoss and IBM call it "JASPI" in e.g. documentation and package names of source code. Oracle calls it "JASPIC". It's a small thing perhaps, but terminology is important and even though the difference is just one letter, it makes searching more difficult since many search engines emphasize full words. In e.g. JIRA and on Google I found searching for just "JASPI" did not always gave me the results that "JASPIC" would give me.

For something that was added to Java EE 6, it really feels out of place that JASPIC is still limited to the Java 1.4 syntax. From Servlet, to JSF to EJB and JPA; pretty much everything has adopted at least the Java 5 syntax. The JASPIC 1.0mr1 spec does mention this issue, but merely states that "There is a requirement that the SPI be used in J2SE 1.4 environments". -Why- this requirement is there, and why it holds for JASPIC but not for most of the other specifications in Java EE 6 is however not really clear.

A serious problem at the moment is the fact that adoption of JASPIC by vendors has been slow. JASPIC may be mandated for a Java EE 6 implementation, but only for the full profile. This means the very important web profile (with implementations like TomEE and Resin) does not need to implement it (not even the Servlet Container Profile, which is a subset of the full JASPIC spec). Web profile implementations do need to implement authentication modules (since security is a mandatory part of both Servlet and EJB-lite), but they have chosen to implement those using their own APIs. This is worrying. JASPIC isn't about something that web profile apps don't need, but is about doing something they need in a specific way. Perhaps this way is not yet good enough or not yet mature enough, or maybe there is just too much investment in proprietary solutions and the advantages of JASPIC are not seen as compelling enough, since otherwise those web profile implementations might have adopted JASPIC of their own account by now, without needing to be forced to implement it. (Tomcat in particular does ship with a JAASRealm (javadoc), which it says is an early prototype of JASPIC that was probably created somewhere around 2004))

Full profile implementations have implemented JASPIC of course, but most present it as a secondary option; for those usecases where a user happens to have a JASPIC authentication module that needs to be used. For "normal" security, the vendors' proprietary solutions are still being presented as the primary solution. As a result, various JASPIC implementations are a little buggy. This is however not a rare situation. The story is rather similar for the standardized embedded data source that was introduced in Java EE 6 (@DataSource or data-source in web.xml). Initially adoption of this was rather slow and most if not all vendors kept plugging their own proprietary ways to define data sources. Lately the situation has improved somewhat, but perhaps the Java EE certification process should be a bit stricter here.

Then there's the question of how to tell a container to use a particular authentication module for a particular application or perhaps for the entire server. In order to do this there are typically a number of options; via some kind of admin UI or console offered by the application server, declarative via configuration files or annotations (where those configuration files can either reside inside a WAR/EAR or inside the server itself), or programmatically via some API.

Unfortunately, the only method that JASPIC standardized is the programmatic option. And this programmatic option seems to be aimed more at vendors needing an internal API to register modules than at user code registering their own at startup. So in practice the already ill-advertised and sometimes buggy standardized method appears to be not that standard at all. The JASPIC documentation of all vendors encourage the user to install the authentication module inside the server, create or edit proprietary configuration files and as if that isn't insulting enough to a developer not rarely requires interacting with a graphical UI as well. Clearly such documentation is aimed at system administrators setting up the kind of traditional servers that are used to run externally obtained applications that need to integrate with the existing infrastructure. Developers creating applications that need to manage their own users are largely left in the cold here. (The GlassFish developer documentation mentions the programmatic option, but doesn't go into detail how that exactly works)

Programmatically registering JASPIC auth modules

Nevertheless, the programmatic API is sort of useable for applications to register their own internal authentication module. Of 5 servers that I tested; JBoss EAP 6.0 (JBoss AS 7.1.2.Final-redhat-1), GlassFish 3.1.2.2, WebLogic 12c, Geronimo v3 and WebSphere 8.5, only Geronimo seemed to have overlooked the possibility for web apps registering their own authentication module. For the other servers it did more or less work, but it's striking that even when using programmatic registration none of them could actually do their job without a vendor specific deployment descriptor being present (which is something related to the general concept of security in Java EE and not a specific fault of JASPIC).

The first hurdle when attempting to use JASPIC for programmatically registering just an authentication module is the fact that there isn't a convenience API to do just that. Instead there's something that's essentially a factory-factory-factory for a delegator to an actual authentication module. That's right, it's a quadruple indirection. Usefull and flexible for those situations that require it no doubt, but more than a little intimidating for novice JASPIC users.

Another hurdle is that the initial factory used for registering the factory-factory requires an "appContext" identifier. This identifier is specified to be either null, or be composed of the pattern [hostname] [space] [context path]. When the identifier is null, the registration is for all (web) applications, otherwise it's only for a specific one. Clearly when an application registers its own internal authentication module the latter form is needed. The problem is that this "hostname" part is not that easy to guess when doing programmatic registration at startup time. It's further defined as being a "logical host", but how does an app knows what its own logical host is? The situation is further complicated by the fact that all servers except JBoss EAP just use a constant here, which is simply "server" in case of GlassFish, Geronimo and WebLogic and "default_host" in case of WebSphere. JBoss EAP however uses ServletRequest#getLocalName here, which is a value that's only available during request processing and not during startup time. It seems likely that if internal application server code is doing both the registration and the subsequent lookups, this is not really a problem. The AS itself knows which key it used for registration and can easily use the same one for lookups later. But when user code needs to do a registration independent of the application server that later on does the lookup, this becomes a problem. Maybe JBoss has interpreted the spec wrongly and the logical host should really be the constant "server", but then the spec needs to be clarified here. If it really should be a logical host of some kind, then there also needs to be a way to express that the application doesn't care about this (for example by specifying "*" as a kind of bind-all). As it stands, the situation is highly confusing. UPDATE: In JASPIC 1.1/Java EE this problem has been solved.

Sample code


The code below shows how to programmatically register a sample JASPIC authentication module. The module itself will be as simple as can be, and always just "returns" a user with a name and one role.

Step 1 - Registering via the factory-factory-factory

We first obtain a reference to the factory-factory-factory (AuthConfigFactory), which we use to register our own factory-factory (shown highlighted). We need to specify for which layer we're doing the registration, which needs to be the constant "HttpServlet" for the Servlet Container Profile. For this example we evade the problems with the appContext and provide a null, which means we're doing the registration for all applications running on the server.
@WebListener
public class StartupListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        AuthConfigFactory factory = AuthConfigFactory.getFactory();
        factory.registerConfigProvider(
      new TestAuthConfigProvider(), 
      "HttpServlet", null, 
      "The test"
     );
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

Step 2 - Implementing the factory-factory

In the next step we look at the factory-factory that we registered above, which is an implementation of AuthConfigProvider. This factory-factory has a required constructor with a required implementation. The implementation seemed trivial; we need to do a self-registration. As I wasn't sure where some of the parameters had to be obtained from, I used my good friend null again.

The real meat of this class is in the getServerAuthConfig method (shown highlighted), which simply has to return a factory. The flexibility that this factory-factory offers is the ability to create factories with a given handler or when this is null give the factory-factory the chance to create a default handler of some sorts. There's also a refresh method that I think asks for updating all factories created by the factory-factory if needed. It's only for dynamic factory-factories though, so I left it unimplemented.
public class TestAuthConfigProvider implements AuthConfigProvider {

    private static final String 
        CALLBACK_HANDLER_PROPERTY_NAME = "authconfigprovider.client.callbackhandler";

    private Map<String, String> providerProperties;

    public TestAuthConfigProvider() {
    }

    /**
     * Constructor with signature and implementation that's required by API.
     * 
     * @param properties
     * @param factory
     */
    public TestAuthConfigProvider(Map<String, String> properties,
            AuthConfigFactory factory) {
        this.providerProperties = properties;

        // API requires self registration if factory is provided. Not clear
        // where the "layer" (2nd parameter)
        // and especially "appContext" (3rd parameter) values have to come from
        // at this place.
        if (factory != null) {
            factory.registerConfigProvider(
                this, null, null,
                "Auto registration"
            );
        }
    }

    /**
     * The actual factory method that creates the factory used to eventually
     * obtain the delegate for a SAM.
     */
    @Override
    public ServerAuthConfig getServerAuthConfig(String layer,
            String appContext, CallbackHandler handler) throws AuthException,
            SecurityException {
        return new TestServerAuthConfig(layer, appContext,
                handler == null ? createDefaultCallbackHandler() : handler,
                providerProperties);
    }

    @Override
    public ClientAuthConfig getClientAuthConfig(String layer,
            String appContext, CallbackHandler handler) throws AuthException,
            SecurityException {
        return null;
    }

    @Override
    public void refresh() {
    }

    /**
     * Creates a default callback handler via the system property
     * "authconfigprovider.client.callbackhandler", as seemingly required by the
     * API (API uses wording "may" create default handler).
     * 
     * @return
     * @throws AuthException
     */
    private CallbackHandler createDefaultCallbackHandler() throws AuthException {
        String callBackClassName = System
                .getProperty(CALLBACK_HANDLER_PROPERTY_NAME);
        
        if (callBackClassName == null) {
            throw new AuthException(
                    "No default handler set via system property: "
                            + CALLBACK_HANDLER_PROPERTY_NAME);
        }

        try {
            return (CallbackHandler) Thread.currentThread()
                    .getContextClassLoader().loadClass(callBackClassName)
                    .newInstance();
        } catch (Exception e) {
            throw new AuthException(e.getMessage());
        }
    }

}

Step 3 - Implementing the factory

The factory that we returned in the previous step is an implementation of ServerAuthConfig. Its main functionality is creating instances of delegators for the authentication module (shown highlighted).

In our case the factory functionality is very simply; it just creates a new instance of the delegator passing only the handler through. The factories that are provided by the application servers themselves typically read-in and process the proprietary configuration files here.

I observed an interesting difference here between Geronimo and the other servers tested; Geronimo calls the getAuthContext method twice per request, while the others only do so once.
/**
 * This class functions as a kind of factory for {@link ServerAuthContext}
 * instances, which are delegates for the actual {@link ServerAuthModule} (SAM)
 * that we're after.
 * 
 */
public class TestServerAuthConfig implements ServerAuthConfig {

    private String layer;
    private String appContext;
    private CallbackHandler handler;
    private Map<String, String> providerProperties;

    public TestServerAuthConfig(String layer, String appContext,
            CallbackHandler handler, Map<String, String> providerProperties) {
        this.layer = layer;
        this.appContext = appContext;
        this.handler = handler;
        this.providerProperties = providerProperties;
    }

    /**
     * WebLogic 12c, JBoss EAP 6 and GlassFish 3.1.2.2 call this only once per
     * request, Geronimo V3 calls this before sam.validateRequest and again
     * before sam.secureRequest in the same request.
     * 
     */
    @Override
    public ServerAuthContext getAuthContext(String authContextID,
            Subject serviceSubject, @SuppressWarnings("rawtypes") Map properties)
            throws AuthException {
        
        return new TestServerAuthContext(handler);
    }

    @Override
    public String getMessageLayer() {
        return layer;
    }

    @Override
    public String getAuthContextID(MessageInfo messageInfo) {
        return appContext;
    }

    @Override
    public String getAppContext() {
        return appContext;
    }

    @Override
    public void refresh() {
    }

    @Override
    public boolean isProtected() {
        return false;
    }

    public Map<String, String> getProviderProperties() {
        return providerProperties;
    }

}

Step 4 - Implementing the delegator

In the delegator (an implementation of ServerAuthContext) that was returned from the factory above we finally get a chance to create our authentication module (shown highlighted).

The rest of the delegator class can be pretty simple. As we don't have any selection between modules to do, we just delegate directly to the one and only module that we encapsulate.
/**
 * The Server Authentication Context is an extra (required) indirection between
 * the Application Server and the actual Server Authentication Module (SAM).
 * This can be used to encapsulate any number of SAMs and either select one at
 * run-time, invoke them all in order, etc.
 * <p>
 * Since this simple example only has a single SAM, we delegate directly to that
 * one. Note that this {@link ServerAuthContext} and the
 * {@link ServerAuthModule} (SAM) share a common base interface:
 * {@link ServerAuth}.
 * 
 */
public class TestServerAuthContext implements ServerAuthContext {

    private ServerAuthModule serverAuthModule;

    public TestServerAuthContext(CallbackHandler handler) throws AuthException {
        serverAuthModule = new TestServerAuthModule();
        serverAuthModule.initialize(null, null, handler,
                Collections.<String, String> emptyMap());
    }

    @Override
    public AuthStatus validateRequest(MessageInfo messageInfo,
            Subject clientSubject, Subject serviceSubject) throws AuthException {
        return serverAuthModule.validateRequest(messageInfo, clientSubject,
                serviceSubject);
    }

    @Override
    public AuthStatus secureResponse(MessageInfo messageInfo,
            Subject serviceSubject) throws AuthException {
        return serverAuthModule.secureResponse(messageInfo, serviceSubject);
    }

    @Override
    public void cleanSubject(MessageInfo messageInfo, Subject subject)
            throws AuthException {
        serverAuthModule.cleanSubject(messageInfo, subject);
    }

}

Step 5 - Implementing the authentication module

At long last, we finally get to implement our authentication module, which is an instance of ServerAuthModule. With respect to the API, it's interesting to note that this time around there's an initialize method present instead of a mandatory constructor.

As mentioned before, we don't do an actual authentication but just "install" the caller principal and a role into the JAAS Subject. For this example, getSupportedMessageTypes actually doesn't need to be implemented since it's only called by the delegator that encapsulates it. Since we own that delegator, we know it's not going to call this method. For completeness though I implemented it anyway to be compliant with the Servlet Container Profile.

Interesting to note is that secureResponse was treated differently by most servers. Only WebLogic and Geronimo call this method, but where WebLogic insists on seeing SEND_SUCCESS returned, Geronimo just ignores the return value. In its class org.apache.geronimo.tomcat.security.SecurityValve, it contains the following code fragment:
// This returns a success code but I'm not sure what to do with it.
authenticator.secureResponse(request, response, authResult);
Another difference for this same secureResponse method, is that WebLogic calls it before a protected resource (e.g. Servlet) is called, while Geronimo does so after.
/**
 * The actual Server Authentication Module AKA SAM.
 * 
 */
public class TestServerAuthModule implements ServerAuthModule {

    private CallbackHandler handler;
    private Class<?>[] supportedMessageTypes = 
        new Class[] {HttpServletRequest.class, HttpServletResponse.class };

    @Override
    public void initialize(MessagePolicy requestPolicy,
            MessagePolicy responsePolicy, CallbackHandler handler,
            @SuppressWarnings("rawtypes") Map options) throws AuthException {
        this.handler = handler;
    }

    @Override
    public AuthStatus validateRequest(MessageInfo messageInfo,
            Subject clientSubject, Subject serviceSubject) throws AuthException {

        // Normally we would check here for authentication credentials being
        // present and perform actual authentication, or in absence of those
        // ask the user in some way to authenticate.

        // Here we just create the user and associated roles directly.

        // Create a handler (kind of directive) to add the caller principal (AKA
        // user principal) "test" (=basically user name, or user id)
        // This will be the name of the principal returned by e.g.
        // HttpServletRequest#getUserPrincipal
        CallerPrincipalCallback callerPrincipalCallback = 
            new CallerPrincipalCallback(clientSubject, "test");

        // Create a handler to add the group (AKA role) "architect"
        // This is what e.g. HttpServletRequest#isUserInRole and @RolesAllowed
        // test for
        GroupPrincipalCallback groupPrincipalCallback = 
            new GroupPrincipalCallback(
                clientSubject, new String[] { "architect" }
            );

        // Execute the handlers we created above. This will typically add the
        // "test" principal and the "architect"
        // role in an application server specific way to the JAAS Subject.
        try {
            handler.handle(new Callback[] { callerPrincipalCallback,
                    groupPrincipalCallback });
        } catch (IOException | UnsupportedCallbackException e) {
            e.printStackTrace();
        }

        return SUCCESS;
    }

    /**
     * A compliant implementation should return HttpServletRequest and
     * HttpServletResponse, so the delegation class {@link ServerAuthContext}
     * can choose the right SAM to delegate to. In this example there is only
     * one SAM and thus the return value actually doesn't matter here.
     */
    @Override
    public Class<?>[] getSupportedMessageTypes() {
        return supportedMessageTypes;
    }

    /**
     * WebLogic 12c calls this before Servlet is called, Geronimo v3 after,
     * JBoss EAP 6 and GlassFish 3.1.2.2 don't call this at all. WebLogic
     * (seemingly) only continues if SEND_SUCCESS is returned, Geronimo
     * completely ignores return value.
     */
    @Override
    public AuthStatus secureResponse(MessageInfo messageInfo,
            Subject serviceSubject) throws AuthException {
        return SEND_SUCCESS;
    }

    @Override
    public void cleanSubject(MessageInfo messageInfo, Subject subject)
            throws AuthException {

    }
}

Step 6 - Setting up declarative security in web.xml

To test our code we first setup a security constraint using web.xml. It will simply require the role architect for all resources in our web application.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

 <security-constraint>
  <web-resource-collection>
   <web-resource-name>Test</web-resource-name>
   <url-pattern>/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
   <role-name>architect</role-name>
  </auth-constraint>
 </security-constraint>

 <security-role>
  <role-name>architect</role-name>
 </security-role>

</web-app>

Step 7 - Setting up the mandatory proprietary descriptors

A very unfortunate and nasty step is that we -have- to setup proprietary deployment descriptors for each container.

The majority of them (all, except JBoss EAP) don't directly accept the roles that our authentication module puts into the JAAS Subject, but forces us to map them. This necessitates a rather silly and pointless mapping where every time we map architect to architect. This will be extra painful when we are building an application that uses say 20 roles and we want to support those 3 servers out of the box. It will mean not less than 60 completely pointless mapping directives have to be added :(

Two servers require us to specify something that JBoss calls a domain, but Geronimo calls a security realm. The idea behind this concept is that it's a kind of alias for a whole slew of security configuration options (typically which authentication modules should be used). Of course, if we're registering our own authentication modules programmatically this is rather pointless as well. JBoss actually seems to want that we modify a file called domain.xml inside the JBoss installation directory (a horror for portable apps that take care of their own security configuration), but luckily there's already a domain defined there by default that we can use. The problem with these default things in JBoss is that JBoss does like to change them on a whim between (major) releases. Today I found a domain called "other" to be useable, but unfortunately I know from experience this might have another name in the next release.

Two servers, Geronimo and JBoss also needed extra configuration to work around bugs, where in the case of JBoss this configuration was needed because despite being Java EE 6 certified JBoss seemingly does not want to make JASPIC available by default; the user has to explicitly activate it. In the case of Geronimo, it was required to specify something called a moduleId, or otherwise ClassNotFoundExceptions would be thrown:
java.lang.NoClassDefFoundError: jaspic/TestServerAuthConfig
 at jaspi.TestAuthConfigProvider.getServerAuthConfig(TestAuthConfigProvider.java:50)
 at org.apache.geronimo.tomcat.BaseGeronimoContextConfig.configureSecurity(BaseGeronimoContextConfig.java:177)
 at org.apache.geronimo.tomcat.WebContextConfig.authenticatorConfig(WebContextConfig.java:51)
 at org.apache.geronimo.tomcat.BaseGeronimoContextConfig.configureStart(BaseGeronimoContextConfig.java:116)


WebSphere 8.5 was particularly troublesome here. The example application is a WAR, but the file in which the roles mapping had to be done could only reside in an EAR. So, specifically for WebSphere an extra wrapping EAR had to be created. Even more troublesome was that with WebSphere security it self first had to be activated in a graphical admin console (by default at https://localhost:9043/ibm/console). It's a well known caveat. After security was activated, JASPIC had to be separately activated as well. There seemed to be an option for this in the proprietary deployment descriptor, but unfortunately this didn't work. Likely this option is there to register a SAM declaratively, and it doesn't do anything without this SAM being given. More precisely the two settings that needed to be changed via the admin console are:
  • Servers -> Server Types -> WebSphere application servers -> Security Domain -> Application Security -> Enable application security
  • Servers -> Server Types -> WebSphere application servers -> Security Domain -> Enable Java Authentication SPI (JASPI)
After that and adding the role mapping, authentication kept failing. The only thing that was logged was: "SECJ0056E: Authentication failed for reason ", which is a rather poor problem description. After hours of searching, the proprietary alternative to the JASPIC callback handlers hinted at a solution. Namely, most JASPIC handlers are just wrappers around whatever proprietary mechanism the server has or had in place before JASPIC. In this case, the alternative solution asked to "get a unique user id" from some "registry". But how does WebSphere know about these users? As it appeared; creating them via the Admin Console again:
  • Users and Groups -> Manage Users -> Create -> [User id = test]
  • Users and Groups -> Manage Groups -> Create -> [Group name = architect]
After this, authentication finally succeeded. However, it's of course undoable to manually add all groups and especially all users to the admin console. How would this even work when users register themselves via the web? Hopefully there's an option somewhere to disable this, but I haven't found it yet.

When it comes to proprietary stuff, Websphere was clearly the worst offender. Having to mock around with a GUI before the app can run is just not tolerable for the kind of application we're trying to build here. But Geronimo was not innocent either. As it stands Geronimo requires both the "security realm" thing and the role mapping to be specified, as well as some gibberish for working around what seems to be a bug.

GlassFish 3.1.2.2
WEB-INF/sun-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
<sun-web-app>

    <security-role-mapping>
        <role-name>architect</role-name>
        <group-name>architect</group-name>
    </security-role-mapping>

</sun-web-app>

WebOTX 9
WEB-INF/nec-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<nec-web-app>

    <security-role-mapping> 
        <role-name>architect</role-name>
        <group-name>stGroup</group-name>
    </security-role-mapping>

</nec-web-app>

WebLogic 12c (12.1.1)
WEB-INF/weblogic.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app.xsd"
    xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app">

    <security-role-assignment>
        <role-name>architect</role-name>
        <principal-name>architect</principal-name>
    </security-role-assignment>

</weblogic-web-app>

JEUS 8
WEB-INF/jeus-web-dd.xml
<?xml version="1.0"?>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="7.0">

    <role-mapping>
        <role-permission>
            <role>architect</role>
            <principal>architect</principal>
        </role-permission>
    </role-mapping>

</jeus-web-dd>

WebSphere 8.5
[EAR]/META-INF/ibm-application-bnd.xml
<?xml version="1.0" encoding="UTF-8"?>
<application-bnd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-application-bnd_1_1.xsd"
    xmlns="http://websphere.ibm.com/xml/ns/javaee"
    version="1.1">

    <security-role name="architect"> 
        <group name="architect" />
    </security-role>
    
</application-bnd>

Geronimo v3.0
WEB-INF/geronimo-web.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web:web-app 
    xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2" 
    xmlns:sec="http://geronimo.apache.org/xml/ns/security-2.0" 
    xmlns:web="http://geronimo.apache.org/xml/ns/j2ee/web-2.0.1"
>
    
    <dep:environment>
        <dep:moduleId>
            <dep:groupId>default</dep:groupId>
            <dep:artifactId>jaspic</dep:artifactId>
            <dep:version>1.0</dep:version>
        </dep:moduleId>
    </dep:environment>
      
    <web:security-realm-name>geronimo-admin</web:security-realm-name>
    
    <sec:security>
        <sec:role-mappings>
            <sec:role role-name="architect">
                <sec:principal 
                    class="org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal"
                    name="architect" 
                />
            </sec:role>
        </sec:role-mappings>
    </sec:security>
    
</web:web-app>

JBoss EAP 6.0.0.GA
WEB-INF/jboss-web.xml
<?xml version="1.0"?>
<jboss-web>
    <security-domain>other</security-domain>
    <valve>
        <class-name>patch.jboss.WebJASPIAuthenticator</class-name>
    </valve>
</jboss-web>
(note that for JBoss EAP/AS normally jaspi.WebJASPIAuthenticator is used to activate it).

It's amazing really how the exact same nonsense mapping of architect to architect can be expressed in so many nearly identical but still different ways.

Step 8 - Implementing a test Servlet

In order to test that a request is getting authenticated, we also need an actual resource. For this I used a simple Servlet that just prints the name of the caller principal. Note that should the authentication module fail to put a caller principal into the JAAS Subject, this will result in a NullPointerException.
@WebServlet(urlPatterns = "/servlet")
public class TestServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        response.getWriter().write(
            "Username " + request.getUserPrincipal().getName()
        );
    }

}

Step 9 - Working around bugs

Of the 4 servers tested, 2 of them have severe bugs that make the sample authentication module and programmatic registration as shown above unusable.

JBoss AS 7.1.1 and JBoss EAP 6 ignore the GroupPrincipalCallback, which makes it impossible to assign any roles. The way the code is setup a not yet mentioned callback, the PasswordValidationCallback, happens to be required even though the JASPIC spec does not require this one to be used at all. I reported this issue in June 2012 along with a proposal for a fix. Since then, the issue has been cloned, and the patch I proposed was committed around half Oktober of that year. Unfortunately, it wasn't included in JBoss EAP 6.0.1/JBoss AS 7.1.3.Final-redhat-4 that was released the following December, but instead is slated for JBoss AS 7.2 and JBoss AS 7.1.4. It might still take a considerable amount of time before any of those two is released. Since the bug appears in a Tomcat Valve, which is the class we explicitly reference in jboss-web.xml it's relatively easy to patch ourselves.

Geronimo v3.0 needs the extra gibberish in geronimo-web.xml, in order to prevent various class not found exceptions. Unfortunately, JASPIC authentication still doesn't work after that. It seems that if a web application registers a JASPIC authentication module, then this registration doesn't take effect for that application itself. In order to make this work we need to start up Geronimo with the app in question deployed, then undeploy the app while the server is still running and immediately deploy it again. After this sequence JASPIC authentication works correctly. An issue for this has been created at https://issues.apache.org/jira/browse/GERONIMO-6423


Step 10 - Taking behavioral differences into account

With respect to the life-cycle of an authentication module and interaction with the rest of the Java platform, no two servers of the ones tested behaved exactly the same.

For all application servers, the authentication module was invoked when a protected resource (the TestServlet from our example) was invoked. This is a good thing, otherwise JASPIC wouldn't be working at all. However, there was no universal agreement on what to do with non-protected resources. JBoss EAP didn't call the SAM in this case, but all other servers did. After an initial successful authentication (e.g. request.getUserPrincipal() subsequently returns a non-null value during the same request), the behavior differed with respect to the follow-up request. JBoss EAP would remember the full authentication, and would not call the SAM again until either the session expired or an explicit call to request#logout was made. All other servers did call the SAM again. In case we didn't re-authenticate again, then WebLogic would still remember the principal (request.getUserPrincipal() would return the one for which we authenticated), but accessing protected resources for which the authenticated principal has the correct roles was still not allowed. GlassFish and Geronimo both didn't remember a single thing.

As for accessing environmental Java EE resources, in both JBoss EAP and Geronimo it was possible to request the CDI bean manager from the standardized "java:comp/" JNDI namespace. GlassFish and WebLogic would throw binding exceptions here. When the SAM was called at the initial point during a request (before Servlet Filters are invoked) then in JBoss EAP the CDI request and session scope were already active. In GlassFish the scope seemed to be active, but when requesting a bean reference (after obtaining the bean manager via a globally accessible EJB), a scary warning was logged: "SEVERE: No valid EE environment for injection of ...". In WebLogic the mentioned contexts definitely weren't active and context not active exceptions were thrown. Geronimo was hard to test at this point, since the SAM seemingly runs in a different class loader. Things changed when request#authenticate was called from e.g. a JSF managed bean. In that case the SAM was invoked, and for most servers the CDI scopes simply remained active. Judging from the call stack between the authenticate() call and the invocation of the SAM, the CDI scopes are most likely also still active for Geronimo, but because of the class loader issues this was again hard to test.

Which brings us to our last point; for all servers the SAM that was embedded and installed by the application would run with the same class loaders as said application, except for Geronimo. Remembering that we needed the trick with the deploy/undeploy/deploy cycle, this perhaps doesn't come as a surprise.

To summarize, with respect to calls to the validateRequest() method of an authentication module, the following differences were observed:

JBoss EAP GlassFish WebLogic Geronimo WebSphere
Invokes SAM for protected resources VVVVV
Invokes SAM for non-protected resources X / V (with optional valve)VVVV
Invokes SAM after authentication XVVVV
CallerPrincipalCallback accepts any name VVVVX (demands name to be known upfront via admin console)
GroupPrincipalCallback accepts any name VVVVX (demands name to be known upfront via admin console)
javax.security.auth.message.MessagePolicy.isMandatory = false in MessageInfo Map for non-protected resourcesV (with optional valve)XXXV
requestDispatcher.forward works X
(but works with proprietary request#getRequest)
VX
(works when CDI is not enabled)
VV/X
(effectively works, but exception is logged)
ValidateRequest can set wrapped request in MessageInfoXX
(almost works, tiny bug prevents it from actually working)
XXX
SAM method called after request#logoutXXXV (cleanSubject())X
Remembers authentication for request.userPrincipal VXVXX
Remembers authentication for protected resources VXXXX
appContext for web module "app" [getLocalName] /appserver /appserver /appserver /appdefault_host /app
"java:comp/" available in SAM VXXV
CDI request/session scope active in SAM (start of request) VV/XXX/?
CDI request/session scope active in SAM (after authenticate call) VVVX/?
Embedded SAM has same class loader as App VVVX

Please note that an X doesn't mean "bad" in all cases. For instance "Remembers authentication" should NOT be done according to the JASPIC spec, so here a X means "good".

Source code

The full example source code can be obtained from Google code.

Conclusion


JASPIC is one of those things that should have been there relatively early (e.g. for J2EE 1.4 if the original timeline would have hold). By now it could have had its ease-of-use treatment in Java EE 5 and subsequent tuning in Java EE 6. Vendors then might not have had the ~10 years worth of their own proprietary technology in place, which perhaps is currently one of the reasons not all of them are embracing JASPIC beyond what the spec mandates.

Originally a JASPIC 1.1 seemed to have been planned for Java EE 6, but eventually this turned into a smaller maintenance release. Given the various issues outlined above, a true JASPIC 1.1 for Java EE 7 would still be very welcome, but as Java EE 7 is nearing completion and to the best of my knowledge no such work has been started, the chance that we'll see any improvements in the short term are slim.

As it stands, JASPIC is not very well known among users and not universally embraced by vendors. Some users that do know JASPIC find it a "little technical". Where unfortunately some vendors go as far as to call it "bloated", other vendors are waiting for more "widespread adoption" before fully embracing it (which is a kind of chicken-and-egg problem).

Despite all this doom and gloom, the fact is that JASPIC -is- here and it really does offer a good portable way to integrate with container authentication. The bugs that are currently pressent in some implementations can of course be fixed and since the API is standardized there's nothing stopping a third party library to offer some convenience utilities that make things a little easier for 'casual' users (like we also see for e.g. JSF and JPA).

All JASPIC really needs now is just that extra little push.

Arjan Tijms


Further reading:

16 comments:

  1. Arjan,

    great post! I'm using your example code in a EE7/glassfish 4 environment.

    You say to set up the security-constraint in web.xml.
    I tried to restrict the constraint to a certain part of the application (say url-pattern = /admin/*). Still validateRequest in the SAM is called for every request even when its not a restricted (admin) resource.

    And idea?

    Thanks
    Christian

    ReplyDelete
  2. >Still validateRequest in the SAM is called for every request even when its not a >restricted (admin) resource.

    >And idea?

    Yes, this is the intended behavior of JASPIC. validateRequest should be called for every request, independent of whether the request is to a protected resource and independent of whether the user is logged-in or not.

    This feature is very important, since it allows a SAM to e.g. automatically log you in when you go the public home page of a site based on say a cookie. This is called preemptive authentication.

    If the request is to a protected resource, the SAM has to do authentication, otherwise it can opt to "do nothing". You can check if the request is to a protected resource via the following code:

    Boolean.valueOf((String) messageInfo.getMap().get(IS_MANDATORY));


    Also see http://code.google.com/p/omnisecurity/source/browse/src/org/omnifaces/security/jaspic/Jaspic.java#179

    ReplyDelete
  3. Great article. Really helpful. Thanks!

    ReplyDelete
  4. What are the pros/cons of using a ServerAuthModule over an AppServPasswordLoginModule?

    ReplyDelete
    Replies
    1. >What are the pros/cons of using a ServerAuthModule over an AppServPasswordLoginModule?

      The ServerAuthModule is a standardized type. It works on all Java EE servers, like JBoss, GlassFish, JEUS, WebLogic, WebSphere, Geronimo, etc.

      With AppServPasswordLoginModule I guess you mean com.sun.appserv.security.AppservPasswordLoginModule. This is a GlassFish specific type that thus only works on GlassFish.

      If you know how to write such module you can't use that same knowledge to implement a similar module on JBoss; you have to relearn from scratch how to do it.

      Delete
    2. Arjan, thanks, and also really informative article! For the sake of argument, let's say I commit to using Glassfish, is there a good argument to using one technique over the other?

      Delete
  5. Hi Arjan Tijms,
    I'm having this exact problem below. Could you help me?

    http://stackoverflow.com/questions/19731679/how-to-obtain-httpservletrequest-in-appservpasswordloginmodule-ssl

    ReplyDelete
    Replies
    1. I'm afraid I can't really help you with that. As you may have noticed I mainly write about Java EE standard auth modules and thus have little or no experience with login modules that are proprietary for a specific container.

      When you use a JASPIC auth module instead of the GlassFish proprietary mechanism you already get the HttpServletRequest instance, so you might want to look at that.

      Delete
  6. Hi Arjan Tijms,

    Would be possible to implement a ServerAuthModule which accepts multiple login methods, let's say username/password or Oauth?

    Do you mind point me on the right direction?

    Thanks,
    Bruno

    ReplyDelete
    Replies
    1. Theoretically yes. A ServerAuthModule is a very low level thing and can basically do anything. It gets called by the container at the start of an HTTP request, and what code it runs itself or what other code it calls is completely up to the module.

      When an authentication (login) has eventually happened by whatever mechanism(s), the ServerAuthModule communicates the username and roles to the container. The container naturally should not know nor care where this username and these roles were obtained from.

      When you do multiple login methods, you yourself have to keep track though of what method was used. If the login is a multi-step thing you have to remember somewhere (e.g. in the session) which method is currently being used (or tried, if you execute multiple ones in sequence).

      Because JASPIC is such a low-level mechanism there's barely any higher level API to help you with this task, you have to do it yourself. The only thing that's there is that the ServerAuthModule should more or less implement 1 method only, while the ServerAuthContext (see example in the article) should be the place where you manage multiple ServerAuthModules (but the container can't enforce this and it's up to you whether you use this specific devision of responsibilities).

      I've been working on and off on a library that provides some of this higher level functionality, and does indeed allow you to choose between various mechanism. It's however not entirely polished yet and a bit difficult to learn from, but maybe it's useful for you anyway: https://github.com/omnifaces/omnisecurity

      Delete
    2. Arjan,

      Thanks for the reply. I've been reading about Login context in JAAS:

      http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/tutorials/AcnOnly.html

      Do you think it might be a way to do it?

      Thanks,

      Bruno Palermo

      Delete
    3. >Do you think it might be a way to do it?

      Unfortunately not. As I've explained in the article and which is even better explained by Raymond K. NG (see references above), Java EE does not automatically know about JAAS.

      Using the Login Context will NOT communicate anything to the Java EE container. There is no automatic connection whatsoever between the JAAS Login Context and Java EE.

      Just think of it as implementing your own class, e.g.:

      public class MyLogin {
      public boolean login;
      }

      Then you execute:

      MyLogin myLogin = new MyLogin()
      myLogin.login = true;

      Now you're logged-in according to the instance of the MyLogin class, but of course it's trivial to see that setting this boolean to true does nothing for Java EE. It's exactly the same with JAAS.

      In order to communicate the login details from a JAAS LoginModule to Java EE a connecting thing is needed. This thing is JASPIC, which is the topic of this article ;)

      Delete
    4. Arjan,

      Great explanation! Thank you very much for your time.

      Delete
  7. Arjan,

    I implemented your example for Glassfish and added a few modifications based on this article:

    https://blogs.oracle.com/nasradu8/entry/loginmodule_bridge_profile_jaspic_in

    But now I'm struggling to integrate with Glassfish SSO solution.

    I hope that maybe you could help me with some points.

    Thanks,
    Bruno Palermo

    ReplyDelete
  8. Regarding WebSphere, you have to specify the user registry to be used by your installation:

    http://www-01.ibm.com/support/knowledgecenter/SSAW57_8.5.5/com.ibm.websphere.nd.doc/ae/tsec_useregistry.html?cp=SSAW57_8.5.5%2F1-8-2-33-2-1&lang=en

    ReplyDelete