What's new in Java EE 7's authentication support?

Java EE 7 comes with a lot of improvements in many areas. Things like JSF 2.2, CDI 1.1, JMS 2.0 and JAX-RS 2.0 have all been massively improved.

However, not all specs have seen a significant revision. A number of them like JSP 2.1 and EJB 3.1 only had a minor revision, called a maintenance release. In this post we'll take a look at the changes that were done for the standardized authentication support in Java EE: JASPIC 1.1 (JSR 196).

The second maintenance release of JASPIC, called Maintenance Release B officially has seen its version bumped from 1.0 to 1.1. It's sometimes confusing, but a small maintenance release can have the same version increment as a significant revision. E.g. JSF 2.1 was a small maintenance release of its prior version 2.0, but EJB 3.1 was a significant revision of its prior version 3.0.

On to the changes in JASPIC 1.1:

Standardized application context identifier (AppContextID) for Servlet Profile

One of the big hurdles when trying to register a JASPIC auth module programmatically (which incidentally is the only standardized way) for use in a Servlet app, is the fact that the API requires a so-called "AppContextID".

This AppContextID has to start with a "logical host" name, but there was no standard way for code to obtain this name.

In JASPIC 1.1 this is now possible via the new Servlet 3.1 ServletContext#getVirtualServerName
method. An example of generating a portable AppContextID:

String getAppContextID(ServletContext context)
 return context.getVirtualServerName() + " " + context.getContextPath();
}

Semi-auto register session

In many of the proprietary (vendor specific) authentication modules that are still in use next to JASPIC you often authenticate once and the container then remembers the authentication until the user logs out, clear his cookies or when the HTTP session expires.

In JASPIC however an authentication module was required to re-authenticate fully manually at the start of each (HTTP) request. In practice this boiled down to storing the authentication details (user name and roles) into the HTTP session after the initial authentication, and then just handing them back to the container at every following call. A relatively simply job, but a tedious one and for many it's a quite unexpected requirement that this needs to be done at all. (even for certified Java EE vendors this can be confusing, as JBoss seemingly misinterpreted the spec and always remembers the authentication)

In JASPIC 1.1 a SAM for the Servlet Profile can now explicitly ask for the container to semi-remember the authentication by putting the key javax.servlet.http.registerSession into the MessageInfo map with as value the string true. An example of setting and getting this:

public void setRegisterSession(MessageInfo messageInfo) {
 messageInfo.getMap().put("javax.servlet.http.registerSession", TRUE.toString());
}

boolean isRegisterSession(MessageInfo messageInfo) {
 return Boolean.valueOf((String) messageInfo.getMap().get("javax.servlet.http.registerSession"));
}
The container will however not fully remember the authentication. The SAM is still called at each request, and the SAM still has to re-authenticate, but it can indicate that it wants to retain ("inherit") the authenticated identity that was previously established by executing the following protocol:
public AuthStatus doValidateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {

    HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
    Principal userPrincipal = request.getUserPrincipal();
        
    if (userPrincipal != null) {   
        handler.handle(new Callback[] { 
            new CallerPrincipalCallback(clientSubject, userPrincipal) }
        );
                
        return SUCCESS;   
    }

    // Rest of auth code here
}
(try/catch omitted for brevity)

For implementors of the spec it's important the realize that the mere usage of the CallerPrincipalCallBack in this way should fully restore the authentication identity for the session, which includes the groups. A requirement like the following should have been added to the spec, but unfortunately wasn't:
If doValidateRequest uses the callback handler to handle a CallerPrincipalCallback constructed with the non-null principal obtained from HttpServletRequest#getUserPrincipal, and the principal was originally established using a CallerPrincipalCallback constructed with the principal name, then the groups must be restablished from the container specific principal.
Hopefully a next revision of JASPIC will contain this clarification, and implementors will in the meantime do the right thing anyway.

Support for Servlet 3's authenticate, login and logout methods

Servlet 3 introduced methods for programmatic login and logout, via the methods authenticate, login and logout on HttpServletRequest.

Unfortunately JASPIC did not officially support any of these methods. All JASPIC 1.0 implementations in fact did call a SAM's validateRequest following a call to HttpServletRequest#authenticate, but the way the various vendors decided to implement the login and logout methods was particularly troublesome.

Nearly all of these implementations called a completely unrelated proprietary login module following a call to login (completely ignoring any configured JASPIC SAM), while logout in most cases was completely ignored as well.

JASPIC 1.1 now defines what should happen when these methods are called. authenticate should indeed have validateRequest called, while for logout the existing cleanSubject method on a SAM should be called. login is explicitly not supported; an exception has to be thrown whenever a JASPIC SAM is configured and this method is called. The following shows an example of what an container could approximately do following a call to HttpServletRequest#logout():

ServerAuthContext serverAuthContext = [...]
MessageInfo messageInfo = [...]
Subject subject = [...]

if (messageInfo == null) {
    messageInfo = new SomeMessageInfo(request, response);
}

if (subject = null) {
    subject = new Subject();
}

serverAuthContext.cleanSubject(messageInfo, subject);

Support for Servlet forwards and includes

A JASPIC SAM for the Servlet Profile has access to both the HttpServletRequest and HttpServletResponse objects. This means among others that such SAM should be able to do forwarding & including following the Servlet API. E.g.:

public AuthStatus doValidateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {
 
    HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
    HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();

    try  
        request.getServletContext()
               .getRequestDispatcher("somepage")
               .forward(request, response);
    } catch (ServletException | IOException e) {
        throw (AuthException) new AuthException().initCause(e);
    }
  
    return SEND_CONTINUE;
}
JASPIC 1.0 was silent about whether this use case should work or not, and in practice it thus indeed didn't work on all implementations. (this is in part due to the very early stage of the request processing in which a SAM is called)

JASPIC 1.1 now mandates implementations to make sure that forwarding & including via a RequestDispatcher works as expected.

Clarifications and editorial corrections

Finally, the JASPIC 1.1 spec document contains some clarifications for what e.g. "after the service invocation" means and corrected an inconsistency in the formal JavaDocs.

While these small changes may not seem as exciting as those done for the specs that got a major revision, it's still a great step forward and addresses some very real pain points that existed in JASPIC 1.0.

A very convenient full list of what was changed in JASPIC 1.1 with links to the diffs in the spec and associated JIRA issues can be found at the JCP site.

Arjan Tijms

Comments

  1. Hi Arjan, after reading your "Implementing container authentication in Java EE with JASPIC" this post filled some of the missing pieces. Thank you!
    I was under the impression that the role-group mapping would be optional in EE7/Glassfish 4. But it still seems to be required.
    Also what do you mean by "semi-remember the authentication"? Is the principal not maintained throughout the session?

    Regards,
    Christian

    ReplyDelete
  2. >I was under the impression that the role-group mapping would be optional in EE7/Glassfish 4. But it still seems to be required.

    Indeed, much to everyone's dismay making it optional did not happen. If you think this is important, please consider voting for the following issue: https://java.net/jira/browse/JAVAEE_SPEC-20

    >Also what do you mean by "semi-remember the authentication"? Is the principal not maintained throughout the session?

    The principal is maintained (remembered), but it's not automatically applied the next request. After asking for the principal to be remembered, GlassFish will call the validateRequest the following method again and if you do nothing, the principal will simply not be available during that request.

    Instead, you have to explicitly tell JASPIC that you want it to use the previously remembered principal (and roles). Since JASPIC has a very minimal API, you can't just say something like "container.useRememberedIdentity()", but instead you have to execute the "protocol" as given in the article.

    If the container would "fully remember the authentication", then one would expect that when you "do nothing" during validateRequest the principal would still be available. There's a JIRA issue for this too, see https://java.net/jira/browse/JASPIC_SPEC-20 (again, if you think this is important, please consider voting for it)

    ReplyDelete
  3. Is cleanSubject also responsible for modifying the messageinfo to remove any authentication tokens from cookies or header for example?

    ReplyDelete
    Replies
    1. Yes, that's the idea indeed. A SAM that implements a remember me function can use that method to remove a remember me cookie.

      Delete
  4. Could you clarify the following?

    I log in using my SAM and I use 'javax.servlet.http.registerSession' as you described. The principle is constructed and the handler is called.

    I check the principle before doing the above and call the handler with the existing principle.

    But when the session expires the principle is still there. So I still call the handler but the container probable checks the expiry and will not create the roles and such based on the principle which ultimately causes a 405.

    So what I need is to remove the somehow stored principle when the session expires.
    Can you tell me how to do this?

    ReplyDelete
  5. Hi Arjan, I have implemented this same solution, but i have a problem after authenticate the user.

    I have a Java EE application deployed in Wildfly 18 that is accessed throw different proxys, so different users can access it using different IPs. When my application does a redirection, for example to the login page, it sends a status code 302 to the IP used by the client. The problem is that this IP is cached when the first client access the application and used for different clients. For example:

    CLIENT_1:

    1- Access the site like http://1.1.1.1:8080/MyApp

    2- He is redirected to http://1.1.1.1:8080/MyApp/login

    3- Successfully shows http://1.1.1.1:8080/MyApp/indexPage

    CLIENT_2:

    1- Access the site like http://2.2.2.2:8080/MyApp

    2- He is redirected to http://2.2.2.2:8080/MyApp/login

    3- When he login in, he is redirected to http://1.1.1.1:8080/MyApp/login

    Cannot access login because user does know nothing about IP 1.1.1.1.

    In class AuthModule implements ServerAuthModule in method validateRequest i call authService.authenticate(username, password) and return AuthStatus.SUCCESS; if the authentication is correct.

    ReplyDelete
  6. Hi Arjan, I have implemented this same solution, but i have a problem after authenticate the user.

    I have a Java EE application deployed in Wildfly 18 that is accessed throw different proxys, so different users can access it using different IPs. When my application does a redirection, for example to the login page, it sends a status code 302 to the IP used by the client. The problem is that this IP is cached when the first client access the application and used for different clients. For example:

    CLIENT_1:

    1- Access the site like http://1.1.1.1:8080/MyApp

    2- He is redirected to http://1.1.1.1:8080/MyApp/login

    3- Successfully shows http://1.1.1.1:8080/MyApp/indexPage

    CLIENT_2:

    1- Access the site like http://2.2.2.2:8080/MyApp

    2- He is redirected to http://2.2.2.2:8080/MyApp/login

    3- When he login in, he is redirected to http://1.1.1.1:8080/MyApp/login

    Cannot access login because user does know nothing about IP 1.1.1.1.

    In class AuthModule implements ServerAuthModule in method validateRequest i call authService.authenticate(username, password) and return AuthStatus.SUCCESS; if the authentication is correct.

    ReplyDelete

Post a Comment

Popular posts from this blog

Jakarta EE Survey 2022

Implementing container authentication in Java EE with JASPIC

Counting the rows returned from a JPA query