I've implemented a JAAS LoginModule
that is working great with Spring 4 and Struts 2.3. This same LoginModule
is also invoked via a ServletFilter
in Tomcat 8.0/8.5 to authenticate and authorize requests to Servlets outside of the Spring framework.
The LoginModule
uses a simple implementation of java.security.acl.Group
and separates the User(s) and Role(s) with two simple implementations of java.security.Principal
. By "simple" I mean the minimal implementations that satisfy the interfaces.
The "User" implementation maps the name
property to a unique username (actually an e-mail address). Since the e-mail address is unique but could change, the account database contains a unique account identifier (GUID) that is used to assign groups, roles and to log service requests (while also anonymizing our users). In my model, the AccountIdentifier
has its own class. Essentially, I have two unique identifiers for accounts, but since the e-mail address needs to be supplied to the LoginModule
for authentication, it ended up being the basis for the User principal.
The account identifier is not currently being propagated through to the Subject
in the LoginModule
, but now I need it in order to log service requests.
I see two ways forward with making the account identifier available via the Subject
, but I am uncertain which is the best practice for JAAS:
Principal
implementation to include an "accountIdentifier" property that is set during LoginModule.commit()
.AccountIdentifier
as a separate Principal
that gets added to the Subject
during the LoginModule.commit()
.The first option would be easiest, but that also seems like it defeats the purpose of segregating Personally Identifying Information from accounts (which is something I need to do in order to satisfy the upcoming European GDPR requirements).
Should I even be adding the "User" principal (the one that contains the e-mail address) to the Subject?
There are several incompatibilities among the JAAS and Servlet specifications regarding authentication and user principles. Because of this, Spring uses a different approach to JAAS integration than Tomcat.
This answer documents a comprehensive method of implementing JAAS Login Modules in a way that accommodates both Tomcat and Spring.
For clarity, the two options for implementing user principles is copied here from the question:
Principal
implementation to include an "accountIdentifier" property that is set during LoginModule.commit()
.AccountIdentifier
as a separate Principal
that gets added to the Subject
during the LoginModule.commit()
.Option 1 has the unfortunate side affect of joining together different forms of Personally Identifiable Information, which in some environments could violate the European GDPR regulations (sessions may be serialized onto disk, and this information would go with it).
Option 2 separates out Personally Identifiable Information, but must be implemented in a way that overcomes several limitations in the Servlet Specification and Tomcat's JAAS implementation.
These limitations are described in detail below, with the bold sections summarizing the main points.
JAASRealm
requires that the backing collection(s) of a Subject
preserve the ordering of the Principal
s.The Tomcat 8.5 JAAS Realm documentation states:
Using JAASRealm gives the developer the ability to combine practically any conceivable security realm with Tomcat's CMA.
but then goes on to state:
Although not specified in JAAS, you should create separate classes to distinguish between users and roles, extending
javax.security.Principal
so that Tomcat can tell which Principals returned from your login module are users and which are roles (seeorg.apache.catalina.realm.JAASRealm
). Regardless, the first Principal returned is always treated as the user Principal.
Note that the above Tomcat documentation uses the phrase "the user Principal". Although the JAAS API recommends implementing users and roles as distinct classes extending javax.security.Principal, this is not compatible with the Servlet Specification because HttpServletRequest.getUserPrincipal() allows only for a single Principal to be returned:
Returns a
javax.security.Principal
object containing the name of the current authenticated user. If the user has not been authenticated, the method returns null.
A strict reading of the above documentation states that it should contain "...the name of the current authenticated user", but in order to satisfy my original goal, I am interpreting this as "...any name or identifier for the authenticated Subject". This corresponds more closely to the com.sun.security.auth.UserPrincipal documentation (i.e., "A user principal identified by a username or account name").
Due to the above limitations in Tomcat's JAASRealm
and the Servlet Specification's HttpServletRequest
, it is clearly important that if the account identifier is to be propagated to the request via a ServletFilter
(which only has access to the current session, request and response), it must be contained in the first Principal
(thus Option 1 in the original question would satisfy this requirement, or Option 2 only if it appears first and I do not need the original username). I believe all I really need is the account identifier, so I am sticking with the second option for now, where I hand an "EmailAddressPrincipal" to MyLoginModule
and I receive an "AccountIdentifierPrincipal" back via the Subject
(i.e., the MyLogin.commit()
adds the "AccountIdentifierPrincipal" as the very first principal).
The JAASRealm
documentation is actually slightly contradictory regarding the precise order of Principals, it depends on which section you're reading:
As this Realm iterates over the Principals returned by Subject.getPrincipals(), it will identify the first Principal that matches the "user classes" list as the Principal for this user
vs.
Regardless, the first Principal returned is always treated as the user Principal.
Principle
s returned by a Subject
.Essentially, if I were to create a ServletFilter
that mimics what JAASRealm
is doing, the authentication would look like this (note the iterator in particular):
final LoginContext loginContext = new LoginContext(MyLoginModule.JAAS_REALM, new DefaultCallbackHandler(username, password));
loginContext.login();
final Subject subject = loginContext.getSubject();
request.getSession().setAttribute("AUTH_USER_PRINCIPAL", subject.getPrincipals(AccountIdentifierPrincipal.class).iterator().next());
request.getSession().setAttribute("AUTH_ROLE_PRINCIPALS", subject.getPrincipals(MyRolePrincipal.class));
Unfortunately, this is in direct conflict with the constructor for javax.security.auth.Subject, which mandates that a java.util.Set is used as the backing collection for Principals. Additionally, the Set.iterator() documentation states:
The elements are returned in no particular order (unless this set is an instance of some class that provides a guarantee).
The earliest access we have to the Subject
is in the LoginModule.initialize()
method, which is something that is unfortunately invoked somewhere in the internals of LoginContext
(I think). This means we have no control over the exact subclass of Set
that is used as the backing collection for Principals
, and therefore no control over their ordering. By the time this arrives to the ServletFilter
, it is a SynchronizedSet, so it isn't even clear what the original class was, or whether re-ordering occurred.
This all indicates that in order for JAASRealm
to work as expected only a single user principal can be provided. There is no interface anywhere in that middle layer that clearly establishes the order of the Subject
Principals
.
When using JAASRealm
, only one Principal
of the declared User type should be added to the Subject
during commit
.
When using JAASRealm
, avoid using multiple User class names.
Violating the above two rules may lead to undefined and/or inconsistent behavior.
AuthorityGranter
for Spring, and a ServletFilter
for non-framework Tomcat servletsFor the sake of Option 2, I'm avoiding the use of JAASRealm
because according to all of the above documentation, it does not faithfully adhere to JAAS. This brings me back to the pure ServletFilter
approach.
The javax.security.auth.Subject contains everything needed for authorization: multiple user principals, roles and ACL groups. Unfortunately, this class is only partially serializable, which means I can't just wrap the class as a Principal
and return it.
In order to satisfy Spring's DefaultJaasAuthenticationProvider, implement an AuthorityGranter to map Principal
s to role names - that provides complete control over how the mapping is performed.
Since AuthorityGranter
isn't available outside the Spring Framework, I also implemented a ServletFilter
that uses a similar approach to map roles for my non-Spring webapp. Temporarily, I am using an HttpServletRequestWrapper
to read the Principal and Roles from session attributes (stored in session during authentication) and override getUserPrincipal and isUserInRole. Ultimately, I will revisit JAASRealm to see if it contains any functionality for handling this piece, but I'm not quite there yet.