Thursday, November 12, 2015

The state of portable authentication for GlassFish, Payara, JBoss/WildFly, WebLogic and Liberty

Almost exactly 3 years ago I took an initial look at custom container authentication in Java EE. Java EE has a dedicated API for this called JASPIC. Even though JASPIC was a mandatory part of Java EE, support at the time was not really good. In this article we'll take a look at where things were and how things are in the current crop of servers in 2015.

To begin with, there were a number of spec omissions in JASPIC 1.0 (Java EE 6). The biggest one was that in order to register a server authentication module (SAM) an application ID had to be provided. This ID could not be obtained in a portable way. The JASPIC 1.1 MR rectified this.

Other spec omissions concerned JASPIC being silent about what would need to happen with respect to HttpServletRequest#login and HttpServletRequest#logout, and with forward and includes done from a SAM. The JASPIC 1.1 MR rectified these omissions too.

With respect to the actual behaviour there were a large number of very serious problems. Most concerned the very basic stateless nature of JASPIC. A JASPIC SAM is like a Servlet Filter; it's called for every request to both public and protected resources, and doesn't automatically create a session when a caller is authenticated. What actually happened differed per server back then. Some only called the SAM for protected resources, some automatically created a session and never called the SAM again, etc.

Another class of problems concerned the life cycle. A SAM has two seemingly simple methods; "validateRequest" that has to be called before Filters and Servlets are invoked, and "secureResponse" that has to be called after. Especially this "after" was ill understood. Some servers called "validateRequest" and "secureResponse" both before the Filters right after each other, while others called "secureResponse" every time data was written to the response.

A specifically peculiar thing was that no server back then was able to wrap the request and response, even though the JASPIC spec clearly states that this is required. Accessing resources from a SAM, such as EJB beans or datasources via JNDI, or CDI beans via the bean manager was a hit or miss as well. Basically every server behaved differently there.

Finally there were big issues with interpreting how portable a SAM should exactly be, and whether the technology should "just be there", or whether some server specific configuration had to be done first. One vendor seemingly interpreted the JASPIC spec as a portable "authentication mechanism" (the artefact that interacts with the user, such as Servlet's FORM), that then delegated to a proprietary (server specific) "identity store" (the artefact that stores the user data and groups, such as LDAP or a database).

In response to this I created a series of tests, that were later donated to the Java EE 7 samples project. Subsequently I worked with all vendors and asked them to improve their JASPIC implementations. With the exception of Geronimo all vendors were very cooperative, so I'd like to take the opportunity here to give them all a big thanks for their hard work.

So after 3 years of creating tests and reporting issues, what's the current situation like? To find out I executed the JASPIC tests against the current crop of servers. The result is shown below:

Running the Java EE 7 samples JASPIC tests
Module Test GlassFish 4.1.1 Payara 4.1.1.154 JBoss EAP 7 alpha1
WildFly 10rc4
WebLogic 12.2.1 Liberty 8.5.5.7
9 beta 2015.10
lifecycle testBasicSAMMethodsCalled
Passed
Passed
Passed
Passed
Passed
lifecycle testLogout
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedPageNotLoggedin
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedPageLoggedin
Passed
Passed
Passed
Failure
Passed
basic-authentication testPublicPageLoggedin
Passed
Passed
Passed
Failure
Passed
basic-authentication testPublicPageNotLoggedin
Passed
Passed
Passed
Passed
Passed
basic-authentication testPublicAccessIsStateless
Passed
Passed
Passed
Failure
Passed
basic-authentication testProtectedAccessIsStateless
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedAccessIsStateless2
Passed
Passed
Passed
Passed
Passed
basic-authentication testProtectedThenPublicAccessIsStateless
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testJSFwithCDIForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testJSFwithCDIForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testJSFIncludeViaPublicResource
Failure
Failure
Failure
Failure
Failure
dispatching-jsf-cdi testJSFForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testJSFForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testCDIForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testCDIForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
dispatching-jsf-cdi testCDIIncludeViaPublicResource
Passed
Passed
Passed
Passed
Failure
dispatching-jsf-cdi testJSFwithCDIIncludeViaPublicResource
Failure
Failure
Failure
Failure
Failure
dispatching testBasicIncludeViaPublicResource
Passed
Passed
Passed
Passed
Failure
dispatching testBasicForwardViaProtectedResource
Passed
Passed
Passed
Passed
Passed
dispatching testBasicForwardViaPublicResource
Passed
Passed
Passed
Passed
Passed
custom-principal testPublicPageLoggedin
Failure
Passed
Passed
Failure
Passed
custom-principal testPublicAccessIsStateless
Passed
Passed
Passed
Failure
Passed
custom-principal testProtectedAccessIsStateless
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedAccessIsStateless2
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedThenPublicAccessIsStateless
Passed
Passed
Passed
Passed
Passed
custom-principal testProtectedPageLoggedin
Failure
Passed
Passed
Failure
Passed
invoke-ejb-cdi protectedInvokeCDIFromSecureResponse
Passed
Passed
Passed
Failure
Failure
invoke-ejb-cdi protectedInvokeCDIFromCleanSubject
Passed
Passed
Passed
Passed
Failure
invoke-ejb-cdi protectedInvokeCDIFromValidateRequest
Passed
Passed
Passed
Failure
Failure
invoke-ejb-cdi protectedInvokeEJBFromSecureResponse
Failure
Failure
Passed
Passed
Passed
invoke-ejb-cdi protectedInvokeEJBFromCleanSubject
Passed
Passed
Passed
Passed
Passed
invoke-ejb-cdi protectedInvokeEJBFromValidateRequest
Failure
Failure
Passed
Passed
Passed
invoke-ejb-cdi publicInvokeEJBFromSecureResponse
Failure
Failure
Passed
Passed
Passed
invoke-ejb-cdi publicInvokeEJBFromValidateRequest
Failure
Failure
Passed
Passed
Passed
invoke-ejb-cdi publicInvokeEJBFromCleanSubject
Passed
Passed
Passed
Passed
Passed
invoke-ejb-cdi publicInvokeCDIFromSecureResponse
Passed
Passed
Passed
Failure
Failure
invoke-ejb-cdi publicInvokeCDIFromValidateRequest
Passed
Passed
Passed
Failure
Failure
invoke-ejb-cdi publicInvokeCDIFromCleanSubject
Passed
Passed
Passed
Passed
Passed
register-session testJoinSessionIsOptional
Passed
Passed
Passed
Failure
Failure
register-session testRemembersSession
Passed
Passed
Passed
Failure
Passed
status-codes test404inResponse
Passed
Passed
Failure
Passed
Passed
status-codes test404inResponse
Passed
Passed
Failure
Passed
Passed
async-authentication testBasicAsync
Passed
Passed
Passed
Passed
Passed
ejb-propagation publicServletCallingPublicEJBThenLogout
Passed
Passed
Passed
Failure
Passed
ejb-propagation protectedServletCallingProtectedEJB
Passed
Passed
Passed
Failure
Passed
ejb-propagation protectedServletCallingPublicEJB
Passed
Passed
Passed
Failure
Passed
ejb-propagation publicServletCallingProtectedEJB
Passed
Passed
Passed
Failure
Passed
wrapping testResponseWrapping
Passed
Passed
Passed
Passed
Passed
wrapping testRequestWrapping
Passed
Passed
Passed
Passed
Passed

 

As can be seen the situation has greatly improved. With the unfortunate exception of WebLogic 12.2.1 the basics now work everywhere. WebLogic 12.2.1 is perhaps a special case as it seems to be hit by a major bug where the most basic version of authentication doesn't work anymore, while it did work in the previous version 12.1.3. The fact that "testProtectedPageLoggedin" and "testPublicPageLoggedin" fail mean that actual authentication doesn't work properly. In this specific case it appears that when a caller authenticates with name "test" and gets the role "architect", then those are not available to the application. E.g. request#getUserPrincipal() still returns null and request#isUserInRole() returns false. This unfortunately means that for the moment until this bug is fixed JASPIC can not really be used on WebLogic at all.

Looking further at the results we see that the seemingly difficult to understand "secureResponse" method is now always called at the correct moment, and wrapping the request and response that once no server was able to do is now working well in all servers.

Forwards are now supported by all servers as are logouts. Includes are supported by most servers, only Liberty seems to have some issues with these. Curiously no server is able to include a resource that uses JSF. This is likely a JSF issue (as a JSF EG member and Mojarra committer this is something I probably have to fix myself ;))

Invoking resources has improved somewhat, but remains troublesome. Neither EJB beans nor CDI beans can be obtained and invoked on every server. EJB (specially those in the app scope such as java:comp, java:app, etc) work on JBoss EAP/WildFly, WebLogic and Liberty, but not on GlassFish and derivative Payara. CDI beans work in GlassFish, Payara and WildFly, but not in WebLogic and Liberty. WildFly is the one server where they both work.

The resources situation is still a spec issue as well and JASPIC 1.1 remains silent on whether this should work or not. The spec lead has clarified that even though the spec is silent on accessing EJB beans and other resources from the web component's JNDI namespaces, this is something that ought to work and GlassFish' current behaviour is just a bug. A next revision of the JASPIC spec should clarify this though. For the CDI beans no such clarification has been given, so vendors can't be asked to support this based on what the spec requires. However, accessing CDI from a SAM is very likely going to be a requirement coming from JSR 375 (Java EE security). So even though JASPIC doesn't mandate this now, it would be good if vendors already supported this in order to be prepared for Java EE 8.

Another case worth looking at is providing a custom principal from a SAM. This is a feature of JASPIC where a SAM can provide its own custom principal, e.g. org.example.MyPrincipal, which then has to be returned from request#getUserPrincipal(). This works on most servers except on GlassFish. It currently also doesn't work on WebLogic, but without further investigation it's hard to say whether it doesn't support this at all, or just because of the earlier failure of making the principal (custom or not) available.

Setting a response status code from a SAM (like e.g. a 404 - NOT FOUND) is something that is supported by all Servers, except for JBoss EAP/WildFly. This is currently the only unique failure for WildFly. Sort of, since it actually has already been fixed, but a build containing that fix has not yet been released.

From the outcome of the tests shown above it would seem JBoss EAP/WildFly clearly has the best JASPIC implementation, but there's one small but very important detail not shown in that table; the question whether JASPIC needs to be activated in a proprietary way. Unfortunately, JBoss EAP/WildFly indeeds needs such activation. If this activation would entail placing a special configuration file in the application archive it wouldn't be so bad, but JBoss EAP/WildFly actually requires the container to be modified before JASPIC can be used. This therefor means a SAM can not be deployed to a stock JBoss EAP/WildFly, which is very unfortunate indeed. There's a programmatic workaround available that doesn't require the container to be modified (see the activation link), but this is rather hacky and may break with every new release of JBoss EAP/WildFly.

The other server that needs server specific configuration is Liberty. Earlier versions of Liberty required all users and groups that a JASPIC SAM handles to be known by Liberty's proprietary user registry. An often downright impossible requirement in general and specifically for fully portable SAMs, and one that even violates the JASPIC spec. The current versions of Liberty have somewhat improved the situation by only requiring groups to be made known to Liberty. While still a very unfortunate requirement, it's at least possible to do this. Still, listing all the groups that an application uses in a proprietary file inside the container is a bit anti to one of the major use cases for which JASPIC is used; portable and application managed custom authentication. Instead of listing all the groups there's a workaround available where a NOOP user registry is installed and configured.

Conclusion

JBoss EAP 7/WildFly 10rc4 are almost perfect, if only JASPIC worked out of the box or could be activated from within the application archive using a configuration file. Payara 4.1.1.154 is another very good server for JASPIC. Here JASPIC works out of the box, but it suffers from a somewhat nasty bug that prevents it from using application scoped JNDI namespaces. GlassFish 4.1.1 is almost as good, but suffers from an extra bug that prevents it from using custom principals.

Liberty is quite good as well. It has slightly more bugs to fix than JBoss and Payara, but about the same as GlassFish. GlassFish can't use custom principals, Liberty can't do includes. Both can't obtain and invoke a specific bean type (for GlassFish this is EJB, for Liberty it's CDI). But above all Liberty suffers from its conflicting user registry requirement, although by far not as badly as before.

WebLogic 12.2.1 can at the moment not be recommended for JASPIC. It suffers from a severe bug that prohibits an application to use the authenticated identity, which is the core of what JASPIC does. Hopefully the WebLogic team is able to squash this particular bug soon.

All in all we've seen there's a steady and definite improvement going on for the various JASPIC implementations, but as can be seen there's still room left for improvement.

Arjan Tijms