Search code examples
javaspringauthenticationtomcatjaas

User Principal in JAAS Login Module - extra attributes as separate Principal or as Bean properties?


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:

  1. Extend my current "User" Principal implementation to include an "accountIdentifier" property that is set during LoginModule.commit().
  2. Implement 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?


Solution

  • 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:

    1. Extend my current "User" Principal implementation to include an "accountIdentifier" property that is set during LoginModule.commit().
    2. Implement 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 Principals.

    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 (see org.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.


    The Servlet API provides no guarantees of the ordering of Principles 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.


    Conclusions

    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.


    Solution: Use an AuthorityGranter for Spring, and a ServletFilter for non-framework Tomcat servlets

    For 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 Principals 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.