JAAS is Terrible... and There Is No Escape From It
I’ve been spending really a lot of time over the last 18 months or so trying to come up with some kind of sane, interoperable, and performant way to provide a uniform WildFly security layer that allows the many features that have been requested of us (remote authentication, authorization propagation, etc.). Over this slow road to madness, I’ve come to realize that we’re at the mercy of a truly awful piece of technology: JAAS. And there’s no escape from it, for you or me.
AccessControlContext = Slow
Everything in JAAS is centered around the concept of a Subject. The concept is simple enough: it represents a complete authorization identity (or identity set) and credential set. When I want to perform some action, the current Subject is how I assert my authorization to do so.. The big question that authorization-sensitive frameworks must answer is, how do I know what my thread’s “current” Subject is? And this is where the trouble starts. When I call Subject.doAs(…) to perform some action as a given identity, the “current” Subject is not, in fact, associated with the running thread; it is actually associated with something called an AccessControlContext. For those who are not aware of what this is, it is used by the standard Java SecurityManager implementation (when it is installed) to check permissions, forming the basis of the modern Java sandboxing system. An AccessControlContext (with the help of some scary native code) is a compilation of an accumulation of “protection domains”, each of which might correspond to a Class on the call stack. Needless to say, determining your current AccessControlContext is a complicated and expensive procedure. It involves traversing the call stack, allocating and releasing lots of objects, and ultimately compacting things into a final object array which is ready to be scanned for the purpose of checking permissions, among other things. So what this means is, in order to know your Subject, you have to compile your AccessControlContext - even if you don’t have a security manager installed. This is a very expensive procedure for something that can be needed many times per application request! Contrast this with a simple ThreadLocal access, which is hundreds (if not thousands) of times faster by my quick & stupid measurements. If you do have the default security manager installed, then you can just forget about performance altogether since you will be performing dozens to hundreds to even thousands of these security checks per request - each with its own AccessControlContext compilation - and chances are that the user’s expectations for actual security (for example, the ability to run actually untrusted code) are completely wrong anyway even after all this slow, expensive, often-redundant work, rendering any supposed “security” completely moot. I’ll save that particular rant for another post, however.
But Wait, There’s More!
Because the AccessControlContext is already used for access checking by the SecurityManager, it makes sense that there are existing constructs to manipulate them in various ways. The AccessController class provides a way to perform so-called “privileged actions” - it gives developers access to a “blank slate” of security where only their code’s permissions affect access decisions, instead of the union of the accumulated permissions of all the callers on the call stack. In other words, it’s a simple way to perform actions that a your callers would not otherwise be able to do. However, there’s one problem - performing privileged actions actually erases the caller’s identity. Not for any good conceptual reason; it’s just how the implementation happend to shake out. The association of a Subject with an AccessControlContext uses something called a DomainCombiner - which is exactly as simple as it sounds (i.e. not simple). But privileged actions create a pristine ACC with no DomainCombiner, meaning if you want the identity of the caller, you have to get it before running your privileged action. Unless you don’t have privileges to get it. Until Java SE 6, the only way you could do this was to capture the current AccessControlContext (expensive!), and pass it in to your PrivilegedAction instance. In Java SE 6 however they added the non-intuitively named “doPrivilegedWithCombiner” method set which can perform a privileged action while preserving the current DomainCombiner, and hence, the current Subject. Clever, eh?
LoginModules and Network Authentication
The SPI which one uses to provide an authentication mechanism - whether it is authenticating against a user database or performing an authorization decision - is the LoginModule. On the surface this is a reasonable enough SPI - implementations use a simple set of passed-in data and the Subject itself to make decisions based on the supplied user credentials, with a pretty clear contract. A system of Callbacks and CallbackHandlers are used to relay authentication decisions The problem is that the API was never considered in the light of remote authentication. If I have a protocol (like SASL) which performs any kind of authentication other than passing a clear password over the wire - even if it is just a challenge-response password authentication protocol of some sort - the LoginModule has to be prepared not only to verify a password, but to provide it to the network authentication mechanism which is doing the real work. In addition, barring some vendor-specific extension, it takes black magic for a LoginModule to know what is expected of it. The idea behind the Callback API is a good one. Network clients can implement a basic CallbackHandler which can generically pop up a dialog box and, without having to write any extra code, be able to handle any authentication scheme that uses the standard callback types (which cover user name, password, and a variety of other inputs) using only information from the passed-in callback list. What happens however when you have a SaslServer (to which you need to provide a CallbackHandler which verifies and authenticates users) that you need to tie in to a JAAS LoginModule (to which you also need to provide a CallbackHandler) on the server side? Well, it doesn’t work. At all. And this is indicative of a greater problem and lack of foresight in the JAAS design. JASPIC Nonsense Out in the wings of JAAS, there is a thing known as JASPIC (JSR-196). I have rarely seen a specification (even in the Java world) so vague and full of nonsense language as this one. This JAAS extension basically seems to serve one purpose: establishing a single Principal as the current identity of a Subject for the duration of a web request. The spec, however, isn’t written to describe a purpose. It describes, and I quote, a “message processing model” (though it’s only peripherally associated with anything a sane person might describe as a “messaging system”, e.g. JMS). ”This specification defines a service provider interface (SPI) by which authentication providers implementing message authentication mechanisms may be integrated in client or server message processing containers or runtimes.” This unnecessary, unusual obfuscatory language lays the basis for a specification that I have never seen concisely described by anyone. I get a strong sense that few people actually know what JASPIC is for, but everyone else is just afraid to admit they don’t understand it. Understanding the implicit and bizarre redefinition of “message processing” in this spec is the key to unlocking its mysteries. By the definition of the illustrious authors, HTTP is a “messaging protocol” and Java EE is a “message processing runtime”. But down here on Earth, nobody implements “message authentication mechanisms” - not on purpose, anyway. There is real, industry-standard language around these concepts, but this spec can’t seem to find it. Perhaps someone inhaled too deeply of the SOA smoke before writing this specification. Or perhaps the ivory tower was just too high.
There is No Escape
Between various Java EE APIs (including JASPIC, but also JACC and other doodads) and SE APIs and their implementations, JAAS is, for better or worse, the security framework. It is tied inexorably to a large number of OpenJDK classes - I counted over 400 usages of Subject alone. And with JAAS comes all the terrible baggage of AccessControlContext and the complex, confusing, and slow security model that comes with it. Providing a performant user experience with WildFly is one of my top priorities. But everything that JAAS stands for runs counter to this goal. That leaves me with few options, none of them good. One possible plan we’re evaluating for WildFly is a more complete JAAS-like alternative framework, maybe even coupled with a completely alternative SecurityManager implementation. This will definitely give us the maximal performance and clean API that we want, but at the cost of losing interoperability and a forcing users to use a non-standard API. This may however be our only option in the end. Another idea I’ve looked into is providing our own complete implementation of JAAS. However, there is no escaping from AccessControlContext as the de-facto security context of Java; I can probably squeeze out a couple of performance cheats but probably nothing that will negate the cost of the JAAS design, unfortunately.