Atom Feed SITE FEED   ADD TO GOOGLE READER

Logging in to a web app using JAAS on JBoss

I recently rebuild the login system for my J2EE application. I've learned a lot about JAAS, JBoss and Servlets and I'd like to share that knowledge here.

JAAS login and the client-login domain
There's a special login domain called client-login that passively stores your login credentials. It does not authenticate you against any application-specific login module. But when you attempt to access a privileged application method, the authorization gatekeeper knows about client-login. It applies those credentials to your application-specific login module.
This means that whenever you login to the client-login domain, you should immediately call a privileged method to ensure that the credentials are valid. If they aren't, a RuntimeException of some variety will be fired. If they are valid, your privileged method will execute successfully.

The client-login domain and servlet containers
Where does the client-login domain store your login credentials? It associates them with the thread that calls LoginContext.login().
This means that if you login from a Servlet, you probably won't continue to be logged in on your next HTTP request. Your login() call has authenticated the randomly-selected thread that serviced your first request, which probably won't be the same thread for subsequent requests. Even worse, another random user of your web application will inherit your credentials if they are serviced on your thread.
Therefore, whenever you call LoginContext.login(), you must also call LoginContext.logout(). On the same thread. Before your request is complete.

Web interaction and servlet containers
Your servlets are probably storing lots of state in the user's HTTPSession, whether you know it or not. If you're using JSF, this might be completely transparent to the programmer. This makes great sense - the server should maintain some state about its clients while they are accessing it.
When your users login, they expect to remain logged in for the duration of their HTTPSession. So you need to somehow save the credentials for your user's LoginContext when the log in, and use them whenever your user needs to perform a privileged action. A very simple way to do this is to store a "user" and a "password" in the HTTPSession upon login. Whenever a privileged action is required fetch the credentials from the session, login, do the action, then logout. This is the 'hello world' of JAAS security with servlets and EJB.

Transparent authentication using filters
Four steps for every privileged method is cumbersome. Instead, we would like it if our servlets could skip the whole login hassle. To implement this, we need servlet filters. A servlet filter allows us to run a little bit of code before and after each request in our web application. In our application, we will log in before each request and log out after it has completed. This is the recommended strategy from Scott Stark's JAAS Howto.
Here's a high-level view of his Filter implementation.
public class JAASLoginFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// login
String username = request.getSession().getAttribute("username");
char[] password = request.getSession().getAttribute("password");
handler = new UsernamePasswordHandler(username, password);
LoginContext lc = new LoginContext("client-login", handler);
lc.login();

// run the servlet
chain.doFilter(request, response);

// logout
lc.logout();
}
}
This code is almost perfect, but there's one major problem with it. It doesn't provide a nice way for our servlet to login while processing a request. This is necessary if our LoginServlet wants to access privileged methods.

My refined login Filter
Instead of saving the LoginContext as a local-variable to the doFilter() method, I tie it to the user's request. This means that I can access the LoginContext anywhere in the request, which proves to be very, very handy. Here's a high-level look at the refinement. As with before, I've stripped casts & exceptions.
public class JAASLoginFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
loginToThread(request);
chain.doFilter(request, response);
logoutFromThread(request);
}
public static void loginToThread(HTTPServletRequest request) {
// login
String username = request.getSession().getAttribute("username");
char[] password = request.getSession().getAttribute("password");
if(username == null || password == null) return;
handler = new UsernamePasswordHandler(username, password);
LoginContext lc = new LoginContext("client-login", handler);
lc.login();
request.setAttribute("login-context", lc);
}
public static void logoutFromThread(HTTPServletRequest request) {
LoginContext = request.getAttribute("login-context");
if(loginContext == null) return;
loginContext.logout();
}
}
With this method, I can safely call JAASLoginFilter.loginFromThread() from any Servlet in my code. This allows my LoginServlet to login and run some privileged methods. The logout will be taken care of for me automatically!
Finally note that for all of this to work I need to first set the username and password on the user's session. I have an extra method on my login filter to take care of this work. There's a symmetrical method that removes the session credentials when the user logs out.

Recap
  • JBoss client-login logs in to the current thread
  • Servlet users want to stay logged in for their entire session
  • To accomplish a session login, save login credentials in the session.
  • Using a Servlet Filter, use the session's credentials to login before a request, and logout after it
  • Using the Request.setAttribute() method, we can access the LoginContext from anywhere in the request
  • Always logout whenever we login. This is easiest if its done automatically by a Filter
  • A user's credentials can be stored in the session and the thread/request.
  • To logout of the session, clear the credentials from the session. The request will be cleared when the request completes.
  • Do you have a running example of all these?
    I've been trying to work around with JAAS + JSF + EJB but it has been difficult. I would appreciate your help.
    Why would you want to log out at the end of every request?

    Why would you want to call your filter method from your servlet? Typically one would have the filter process whatever it needs (i.e. login) then hand of to the servlet...

    Wouldn;t you want to avoid putting the password in every request? Safer to only pass it in on the login, then pass around a token (any java class you need) type object instead?
    Wouldn't it be to heavy to call all this login code for each request. Isn't it possible to store the authenticated subject in the session and use it whenever privileged actions have to be taken?
    The code, as pasted doesn't work (or even compile).

    1. Cast the request to the appropriate object, i.e.

    loginToThread((HttpServletRequest) request);


    2. Cast the attribute fetches to the appropriate objects (String), i.e.

    String username = (String) request.getSession().getAttribute("username");


    3. You have the wrong call on your 'logout' method. I'm 99.999% sure you meant to call 'logout' not login. Also, you need to cast things properly, i.e.

    public static void logoutFromThread(HttpServletRequest request) {
    LoginContext lc = (LoginContext) request.getAttribute("login-context");
    if (lc == null)
    return;
    try {
    lc.logout();
    } catch (LoginException le) {
    // todo
    }
    }
    Whoops; fixed the login/logout problem. I've omitted the casts and exceptions for readability.
    That's cool (leaving out casts/exceptions), probably the right approach for making short, readable code for a blog post.

    Would you store the username and password in your filter code? I think I would do that to wrap this stuff in its own neat little box. That is, add a few lines of code after your login and setting of the context [in loginToThread()]:


    lc.login();
    request.setAttribute("login-context", lc);
    request.getSession().setAttribute("username", username);
    request.getSession().setAttribute("password", password);


    One probably doesn't want to pass along password as a request parameter on every call. This opens up other potential issues of load balancing, etc. but that another post altogether, right?

    By the way, I happened to attend your Google Guice talk at JavaOne this year. Well done.