JAAS in Java EE is not the universal standard you may think it is

The Java EE security landscape is complex and fragmented. There is the original Java SE security model, there is JAAS, there is JASPIC, there is JACC and there are the various specs like Servlet, EJB and JAX-RS that each have their own sections on security.

For some reason or the other it's too often thought that there's only a single all encompassing security framework in Java EE, and that the name of that framework is JAAS. This is however not the case. Far from it. Raymond K. NG has explained this a long time ago in the following articles:

In short, Java EE has its own security model and over the years has tried to bridge this to the existing Java SE model, but this has never worked flawlessly. One particular issue that we'll be taking a more in-depth look at in this article concerns the JAAS Subject.

The JAAS Subject represent an entity like a person and is implemented as a so-called "bag of Principals". This means it holds a collection of arbitrary Principals, where a Principal can be anything that identifies the entity, like an email, telephone number or an identification number. This gives it great flexibility to work with, but since none of those Principals are in any way standardized also makes it difficult and open to interpretation. In a way one might almost just pass a plain HashMap along, which after all is very flexible too.

For Java EE most of the flexibility that the Subject offers is lost however. The most predominant APIs only deal with 2 specific principals; the caller/user principal and the group/role principal.

An enormous amount of complexity and rather arcane API workarounds have spun around a simple but very unfortunate fact; those two core principals have not been standardized in any way in Java EE. This means that a JAAS login module, which populates a Subject with principals when it commits, can never be used directly with an arbitrary Java EE server as it has no idea how a particular Java EE server represents the caller/user and group/role principals.

As explained in a previous article JASPIC has introduced a workaround for this, where a so-called CallerPrincipalCallback and GroupPrincipalCallback do represent the data for those two principals in a standardized way. The idea is that a container can read this data from those callbacks in this standardized way and then construct container specific principals which are subsequently stored in a container specific way into the Subject. This works to some degree, but it remains a crude workaround.

The problem is that the JASPIC CallbackHandler to which these Callbacks are passed is under no obligation to just read the data and directly store it into the Subject without any further side effects. In practice, this is thus indeed not what happens. Handlers will set global security contexts, call into services to execute all kinds of login logic and what have you. Furthermore, there is nothing available to directly read those Principals back from a given Subject. Indirectly you can get the user/caller principal back via e.g. the Servlet API's HttpServletRequest#getUserPrincipal, but this only works for the current authenticated user and not for an arbitrary Subject that you may have lying around. Likewise, the role/group principals can be obtained via the JACC API. Unfortunately JACC itself is a rather obscure API that while being part of Java EE does not necessarily actually has to be active at runtime by default, and in fact doesn't even have to work out of the box since Java EE implementations are not mandated to ship with an actual default JACC provider (they only have to provide the SPI to plug such provider into the container). Update: Officially it actually IS required, but the TCK doesn't test for it and nobody has challenged any implementations, so in practice indeed not all servers are shipped with a default JACC provider.

The big question is then of course why these two core principals have never been standardized. Do containers use such different representations that standardization is not feasible, or did it just never occurred to anyone in all those years that it may be handy to standardize these?

In order to see if vastly different representations might be the issue I took a look at how actual containers exactly store the Principals inside a Subject. This was primarily done by examining an instance of a Subject via a debugger, but I also took a quick look at some example JAAS modules for a couple of servers. After all, at some point the JAAS module has to add the Principals to the given Subject and the example may indicate how this is done for a specific server.

Examining an instance of a Subject via a debugger


For authentication I used the JASPIC auth module that was introduced in step 5 of the aforementioned previous article and the same test application. In order to retrieve the Subject I used the following JACC code in the Servlet:
Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");

For JBoss EAP 6.2, GlassFish 4 and Geronimo 3.0.1 this worked perfectly. There were however issues with WebLogic 12.1.2, WebSphere 8.5 and JEUS 8.

In WebLogic 12.1.2 JACC is not enabled by default. You have to explicitly enable it by starting the container with a huge string of command line options, including one that references the directory where WebLogic is installed:

./startWebLogic.sh -Djava.security.manager -Djavax.security.jacc.policy.provider=weblogic.security.jacc.simpleprovider.SimpleJACCPolicy -Djavax.security.jacc.PolicyConfigurationFactory.provider=weblogic.security.jacc.simpleprovider.PolicyConfigurationFactoryImpl -Dweblogic.security.jacc.RoleMapperFactory.provider=weblogic.security.jacc.simpleprovider.RoleMapperFactoryImpl -Djava.security.policy==/opt/weblogic12.1.2/wls12120/wlserver/server/lib/weblogic.policy

A particular nasty requirement here is that WebLogic requires the Java SE security manager to be activated. The Java SE security manager is used for code level protection, which is a level of protection that is rarely if ever needed in Java EE as it's extremely rare that a server will run untrusted code (like e.g. a browser which runs an untrusted Applet from the Internet). Activating the security manager can have a huge performance impact and this is typically not recommended for application servers. Yet, WebLogic for some reason as the only server in the string of servers that I tested requires this. Update: WebLogic 12.1.3 dropped this requirement.

After starting WebLogic this way it crashed immediately with an exception about not having access to read some internal file. To quickly remedy this I took the extreme measure of just granting anything to everything, which kinda beats using the security manager in the first place and which more or less proves the unnecessity of the WebLogic security manager requirement. To do this I put the following in the referenced policy file:

grant { 
    permission java.security.AllPermission;
};

Needless to say this is for testing only and should not be used for a production environment where code level security really is required. After this workaround WebLogic booted, but then started to throw out of memory exceptions. These appeared to be caused by a sudden lack of memory in the permanent generation. After I boosted this from 256MB to 512MB WebLogic was stable again.

WebSphere 8.5 proved to be even worse. Here too JACC is not enabled by default, but it can be enabled via the admin console in the security section. However WebSphere does not seem to ship with any default JACC provider. There's only a provider available that's a client for something called the Tivoli Access Manager, which after some research appeared to be some kind of authorization server that needed to be installed separately. For a moment I pondered whether I should try to install this, but the WebSphere installation had taken a horrendous amount of time already and it would be over the top and an immense overkill to have an external authorization server running just to get the thread local Subject inside a Servlet.

"Luckily" there's also an IBM proprietary way to get the Subject, so I used that instead:

Subject subject = com.ibm.websphere.security.auth.WSSubject.getCallerSubject();

Finally in JEUS 8 too JACC is not enabled by default. It can be enabled by commenting out or removing the existing repository-service and custom-authorization-service elements in the authorization section of [jeus install dir]/domains/jeus_domain/config/domain.xml and adding the empty jacc-service element. It then becomes just this:

<authorization>
    <jacc-service/>
</authorization>

Although there's no security manager used here and thus no overhead from that, TMaxSoft warns in its JEUS 7 security manual that the default JACC provider that will be activated by the above shown configuration is mainly for testing and doesn't advise to use it for real production usage. Curiously though, I found after some experimentation that the non-JACC default authorization provider in JEUS is pretty much just like JACC but with some small differences. This may be an interesting thing to take a deeper look at in a future article.

Additionally JEUS 8 also offered a proprietary way to get hold of the Subject when JACC is not enabled:

Subject subject = jeus.security.impl.login.CommonLoginService.doGetCurrentSubject().toJAASSubject();

Sadly JEUS 8 is the only application server that despite being Java EE 7 certified doesn't implement JASPIC in such a way that it actually works; an authentication module can be installed and it's correctly called for each request, but then the container just does nothing with the user/caller and group/roles that are passed to the handler. Because of this a native login module had to be used for JEUS 8 (the default file based username/password module).

The following shows the result of inspecting the Subject on every server:

JBoss EAP 6.2

Principals
    org.jboss.security.SimpleGroup (name=CallerPrincipal)
        members
            org.jboss.security.SimplePrincipal (name=test)

    org.jboss.security.SimpleGroup (name=Roles)
        members
            org.jboss.security.SimplePrincipal (name=architect)

GlassFish 4.0

Principals
    org.glassfish.security.common.PrincipalImpl (name = test)
    org.glassfish.security.common.Group (name = architect)

Geronimo 3.0.1

Pricipals
    org.apache.geronimo.security.realm.providers.GeronimoUserPrincipal (name="test")
    org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal (name="architect")
    org.apache.geronimo.security.IdentificationPrincipal
        (name = org.apache.geronimo.security.IdentificationPrincipal[[1392054106031:0x0f9d4c68befbfbee189e0ca0dadd3757d577da8b]]
        (id = SubjectId (name = [1392054106031:0x0f9d4c68befbfbee189e0ca0dadd3757d577da8b], subjectId = 1392054106031))

WebLogic 12.1.2

Principals
    weblogic.security.principal.WLSUserImpl (name = test)
    weblogic.security.principal.WLSGroupImpl (name = architect)

WebSphere 8.5

Principals
    com.ibm.ws.security.common.auth.WSPrincipalImpl (username = test, fullname = defaultWIMFileBasedRealm/test)

PublicCredentials
    com.ibm.ws.security.auth.WSCredentialImpl
        (accessId = user:defaultWIMFileBasedRealm/uid=test,o=defaultWIMFileBasedRealm
         groupIds
            group:defaultWIMFileBasedRealm/cn=architect,o=defaultWIMFileBasedRealm
         realmuniqueusername = defaultWIMFileBasedRealm/uid=test,o=defaultWIMFileBasedRealm
         uniqueusername = uid=test,o=defaultWIMFileBasedRealm
         username = test)

JEUS 8

Principals
    jeus.security.resource.PrincipalImpl (name = test)
    jeus.security.resource.GroupPrincipalImpl 
        (name = architect, 
         description =, 
         individuals = {Principal test=Principal test} (Hashtable of jeus.security.resource.PrincipalImpl to jeus.security.resource.PrincipalImpl), 
         subgroups = [] (empty Vector))
    jeus.security.base.Subject$SerializedSubjectPrincipal (bytes = …)

PrivateCredentials
    jeus.security.resource.Password (algorithm = "base64", password = "YWRtaW4wMDc=", plainPassword = "admin007")
    jeus.security.resource.SystemPassword (algorithm = null, password = "globalpass", plainPassword = "globalpass")

So there you have it; 6 different servers implementing pretty much the same thing in 6 different ways.

What we see is that there are several methods to distinguish between a Principal that represents a user/caller name and one that represents a group/role name. JBoss EAP 6.2 as the only tested server uses a named Group for this. The name of the Group indicated the type of the Principal. Not entirely consistently this is "CallerPrincipal" for the user/caller Principal and "Roles" for the Group/Roles Principals. Pretty much every other server uses the class type for this type of distinction. Of course every server uses its own type. Things like org.glassfish.security.common.PrincipalImpl, org.apache.geronimo.security.realm.providers.GeronimoUserPrincipal, weblogic.security.principal.WLSUserImpl and jeus.security.resource.PrincipalImpl are completely identical; a simple Principal implementation with a single String attribute called "name".

WebSphere 8.5 does do things a little different. It only models the user/caller name as a Principal and when doing that for some reason needs an additional "fullname" attribute, but otherwise this is still pretty much the same thing as what the other servers use. Things are more different when it comes to the group/roles. As the only server tested WebSphere doesn't put these in the Principals collection, but uses the private credentials one for this. This collection is of the general type Object, and thus can hold other things than just Principals. It can be argued whether this is more correct or not. Principals are supposed to identify an entity, but does a group/role identify an entity, or is it more a security related attribute? At any length, WebSphere is the only one that thinks the latter.

We also see that two servers store extra internal data into the Subject. Geronimo for some reason needs a org.apache.geronimo.security.IdentificationPrincipal, while JEUS needs an extra jeus.security.base.Subject$SerializedSubjectPrincipal. In case of JEUS the extra Principal seems to be used to convert from its own proprietary Subject type to the standard JAAS Subject type and back again. It looks like JEUS is the only server that thinks it absolutely needs its very own Subject type. From a quick glance at the Geronimo code it wasn't clear why Geronimo needs the IdentificationPrincipal, but I'm sure there is a solid reason for that.

Finally the group/role Principal in JEUS 8 is special in that it holds a reference to the collection of all individuals who are in that group/role. It may be that this specific linkage is only done when using a small build-in user repository like the local file based ones. In case of an external provider like Facebook it would of course really not be feasible. Also note that the credentials are there in JEUS 8 since we really presented a password when we used the server provided login module. In all other cases we used JASPIC and didn't provide a password.

Looking at example JAAS login modules

As mentioned above, JAAS example login modules are another source that's worth looking at. Specifically interesting is the commit method, where the module is supposed to do the transfer from the user/caller name and group/role names into the Subject.

Geronimo 3.0.1 comes with several example modules. Of those the commit methods are conceptually identical. Given below is the method from the PropertiesFileLoginModule:

    public boolean commit() throws LoginException {
        if(loginSucceeded) {
            if(username != null) {
                allPrincipals.add(new GeronimoUserPrincipal(username));
            }
            for (Map.Entry<String, Set<String>> entry : groups.entrySet()) {
                String groupName = entry.getKey();
                Set<String> users = entry.getValue();
                for (String user : users) {
                    if (username.equals(user)) {
                        allPrincipals.add(new GeronimoGroupPrincipal(groupName));
                        break;
                    }
                }
            }
            subject.getPrincipals().addAll(allPrincipals);
        }
        // Clear out the private state
        username = null;
        password = null;

        return loginSucceeded;
    }

In the security manual of JEUS 7 a couple of JAAS login modules are given as well. Given below is the commit method for the DBRealmLoginModule (there is an English version available as PDF, but it's often moved around and requires a login and is thus unfortunately very hard to link to)

    public boolean commit() throws LoginException {
        if (succeeded == false) {
            return false;
        } else {
            userPrincipal = new PrincipalImpl(username);
            if (!subject.getPrincipals().contains(userPrincipal))
                subject.getPrincipals().add(userPrincipal);

            ArrayList roles = getRoleSets();
            for (Iterator i = roles.iterator(); i.hasNext();) {
                String roleName = (String) i.next();
                logger.debug("Adding role to subject : username = " + username +
                        ", roleName = " + roleName);
                subject.getPrincipals().add(new RolePrincipalImpl(roleName));
            }

            userCredential = new Password(password);
            subject.getPrivateCredentials().add(userCredential);

            username = null;
            password = null;
            domain = null;
            commitSucceeded = true;
            return true;
        }
    }

JBoss ships with a number of login modules, which are essentially JAAS login modules as well. They have abstracted the common commit method to the base class AbstractServerLoginModule, which is shown below:

    public boolean commit() throws LoginException {
        PicketBoxLogger.LOGGER.traceBeginCommit(loginOk);
        if (loginOk == false)
            return false;

        Set<Principal> principals = subject.getPrincipals();
        Principal identity = getIdentity();
        principals.add(identity);
        
        // add role groups returned by getRoleSets.
        Group[] roleSets = getRoleSets();
        for (int g = 0; g < roleSets.length; g++) {
            Group group = roleSets[g];
            String name = group.getName();
            Group subjectGroup = createGroup(name, principals);
            if (subjectGroup instanceof NestableGroup) {
                /*
                 * A NestableGroup only allows Groups to be added to it so we need to add a SimpleGroup to subjectRoles to contain the roles
                 */
                SimpleGroup tmp = new SimpleGroup("Roles");
                subjectGroup.addMember(tmp);
                subjectGroup = tmp;
            }
            // Copy the group members to the Subject group
            Enumeration<? extends Principal> members = group.members();
            while (members.hasMoreElements()) {
                Principal role = (Principal) members.nextElement();
                subjectGroup.addMember(role);
            }
        }
        
        // add the CallerPrincipal group if none has been added in getRoleSets
        Group callerGroup = getCallerPrincipalGroup(principals);
        if (callerGroup == null) {
            callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
            callerGroup.addMember(identity);
            principals.add(callerGroup);
        }

        return true;
    }

As can be seen the JAAS modules all use the server specific way to store the Principals inside the Subject and this is done in pretty much the same way as the JASPIC container code did. A remarkable difference is that the JBoss JAAS module inserts the caller/user Principal twice; once directly in the root of the principals set and once in a named group, while the JASPIC auth code only used the named group. In the example DBRealmLoginModule of JEUS there's no trace of a reference to all users in a group/role. So this is either just an extra thing, or perhaps JEUS adds this information to the Subject after the JAAS login module has committed. For this article I did not investigate that further.

Conclusion

We looked at a relatively large number of servers here, nearly all the ones that implement the full Java EE profile (and thus support both JASPIC and JACC where the Subject type comes into play). There are more servers out there like Tomcat, TomEE, Jetty and Resin, but I did not look into them. From previous experience I know that Tomcat doesn't use the Subject type internally and only has support to read from a Subject via its JAAS realm. There are a small number of rather obscure other full Java EE implementations like the Hitachi and NEC offerings which I might investigate in a future article.

For the servers tested it seems that most servers could simply use two standardized Principals for the caller/user and group/role Principal. E.g.

  • javax.security.CallerPrincipal
  • javax.security.GroupPrincipal

Each principal would only need a single attribute name of type String. GlassFish, Geronimo and WebLogic could use such types as a direct replacement. JBoss would have to switch from looking at a group node to the class type of the Principal. JEUS could directly use the standard type for the caller/user principal, but may have to stuff the extra information it now puts in to the group/role principal somewhere else (assuming that it indeed really needs this info and we did not just observed some coincidental side-effect of the particular login-module that we used). The only server that really does things differently is WebSphere. Its caller/user principal is mostly equivalent to those of the other servers, but the group/role one is radically different.

To accommodate the extra info a few servers may need to store in the Principal, it might be feasible to define an extra Map<String, Object> attribute on the standardized Principals where this extra server specific info could be stored.

All in all it thus seems this standardization should be possible without too many problems. The introduction of these two small types would likely simplify the far too abstract and complex Java EE security landscape a lot. The question remains though why this wasn't done long ago.

Arjan Tijms

Comments

  1. Nice work, it's really cool to have this quite exhaustive kind of guide to various implementations. I hope this ever gets specified, it's really one the last aspects that's not-so-portable. Thanks!

    ReplyDelete

Post a Comment

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

What’s new in Jakarta Security 3?

Jakarta EE Survey 2022