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
client-login
logs in to the current thread