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 domainThere'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 containersWhere 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 containersYour 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 filtersFour 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 FilterInstead 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.
RecapJBoss 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.
# posted by Jesse Wilson
on Wednesday, April 27, 2005
6 comments
post a comment
Type safety bites me right in the ass!
Premise:I'm doing some J2EE boilerplate coding to get our authentication system working again. To get started, here's the API of interest.
public class HTTPSession {
public void setAttribute(String name, Object value);
public Object getAttribute(String name);
.....
}
public class UsernamePasswordHandler {
public UsernamePasswordHandler(String username, char[] password);
public UsernamePasswordHandler(String username, Object credential);
}
So far, so good. Now to keep the user authenticated between HTTP requests, I keep their username and password in the user's session. This might be a bad practice, but I couldn't come up with a reasonable alternative. I need to access login on every request, since JBoss authenticates against the calling thread and I'm not guaranteed to get the same thread for different HTTP requests by the same user.
The seemingly harmless code private static LoginContext tryLoginToThread(HttpServletRequest request) throws LoginException{
String principal = (String)request.getSession().getAttribute("principal");
Object credential = request.getSession().getAttribute("credential");
UsernamePasswordHandler userLogin = new UsernamePasswordHandler(principal, credential);
LoginContext loginContext = new LoginContext("client-login", userLogin);
loginContext.login();
return loginContext;
}
The problem?When I run this, my login() method doesn't do anything, but it's not supposed to. It doesn't throw an exception if login fails because in JBoss your login isn't actually authorized until necessary. This isn't a horrible design but it did provide some nice indirection in debugging this problem.
The problem?Here's a bit of the implementation of JBoss' UsernamePasswordHandler class:
if(password == null && credential != null) {
password = credential.toString().toCharArray();
}
The problem!The password I'm using isn't what I think it is. It's supposed to be
riders05cfl
but in reality its
[C@92d342
. That's what you get when you call "riders05cfl".toCharArray().toString().
Although I was calling the constructor with a String and a char[], the compiler didn't call UsernamePasswordHandler(String,char[]). Since it didn't know my credentials object was a character array, it called the other constructor: UsernamePasswordHandler(String,Object). By using the wrong constructor, my password is silently changed from "riders05cfl" to "C@92d342" and it sucks to be me. For reference, here's the fixed code. Note the type of the credentials object.
private static LoginContext tryLoginToThread(HttpServletRequest request) throws LoginException{
String principal = (String)request.getSession().getAttribute("principal");
char[] credential = (char[])request.getSession().getAttribute("credential");
UsernamePasswordHandler userLogin = new UsernamePasswordHandler(principal, credential);
LoginContext loginContext = new LoginContext("client-login", userLogin);
loginContext.login();
return loginContext;
}
Yuck. The moral of the story:
it doesn't matter what the object is, it only matters what the compiler thinks it is.
# posted by Jesse Wilson
on Tuesday, April 26, 2005
0 comments
post a comment
Get XOM
XOM has the best API ever.
In my app we churn business objects into XHTML then XSL:FO and finally PDF. XOM makes it super easy to build the XHTML tree. And if I play my cards right, I might be able to turn that XHTML into FO without serializing it to bytes first. Amazing.
XOM makes XML fun again! Get rid of SAX, DOM and hardcoded
"<html>"
. Get XOM, be happy.
# posted by Jesse Wilson
on Thursday, April 21, 2005
1 comments
post a comment
JButton borders
With Swing's Windows look & feel, you can remove the border from a JButton by replacing it with null:
JButton myButton = ...
myButton.setBorder(null)
Unfortunately this code has
no effect in the Aqua look & feel!
Therefore whenever you remove the border from a JButton, replace it with an empty border:
JButton myButton = ...
myButton.setBorder(BorderFactory.createEmptyBorder())
This is annoying because the
null
seems to work. It's a subtle instance of
Write
Once,
Debug
Everywhere!
# posted by Jesse Wilson
on Thursday, April 21, 2005
2 comments
post a comment
I pity the fool who don't report with FO
I needed a reporting system to replace
JFreeReport. Although it's a decent project I found it very cumbersome to create reports with dynamic columns. My solution is to create the report in XHTML, and use FO to convert that into PDF. It's very easy to code my document's tables using XHTML, and very well understood. This is an overview of that solution.
First off, some FO projects:XSL-FO an XML schema that describes a paper document. I like to think of it as an XML alternative to PDF. It's also a W3C recommendation.
Apache FOP an open source renderer for FO coded in Java. It's
mostly feature complete. Apache FOP takes a FO file and sends it to PDF, a printer, or the screen. This is the FO implementation I used.
Antenna House a commercial renderer for FO. It's $1250 for a client and $5000 for a server. They have some useful FO documentation on their site, but their XSL transformation won't work with Apache FOP.
RenderX another commercial renderer for FO. It's probably more complete than FOP but it costs $299 for a client and $4000 for a server.
XHTML to FOIts fairly straightforward to convert an XHTML document into a FO document. DeveloperWorks has a
fantastic article describing the whole process. The article includes a starter XSLT transformation to get you started. The transformation is well documented and it was easy for me to tweak it for my reporting system. I made the following changes:
Removed table of contents
Changed left footer to document title
Changed right footer to page count
Removed headers
Added an XML namespace for XHTML. This requires the document's <html> tag to include the namespace. For example: <html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
The finished stylesheet creates a nice PDF that includes a "table of contents" for navigating the document easily.
FOP's Limited XHTML
The biggest limitation of Apache's FOP is that it requires that column widths be specified in advance. To overcome this, we have to modify the tables in our XHTML file to specify these column widths. Widths must be specified in points, which is 1/72 of an inch. Fortunately the math is pretty easy. For simplicity assume our page is letter size (8.5" x 11") with 1" margins. Then we have 6.5x72=468 points to distribute over our columns. If we have 3 columns, thats 156 points each. This give us this XHTML table tag: <table border="1" cols="156pt 156pt 156pt">
Conclusion
XHTML rules. Everyone understands it and it's easy to generate. This provides a nice mechanism for your Java program to create nice PDF reports using XHTML.
# posted by Jesse Wilson
on Wednesday, April 20, 2005
1 comments
post a comment
Java ID3 MP3? Good luck!
Today we attempted to parse some MP3 files with Java and we ran into some frustration. We're using the latest iTunes to do ripping and require compatibility with the latest and greatest of ID3v2. Here's what I found:
de.ueberdosis.mp3info The API for this package includes a class called "TwoStrings" that has the obvious methods setOne(), setTwo(), getOne(), getTwo(). This type of genius design didn't offer much confidence. It failed to parse all the MP3s I threw at it.
de.vdheide.mp3 This package offers a tight concise API. The API was really easy to use and I was up-and-running in minutes. But it didn't parse my files, so I just kept running.
helliker.id3 The first ID3 library that partially worked. It has a nice API but has been neglected for over 2 years. It didn't work on ID3v2 but it gave some hope on ID3v1. Still insufficient though.
org.farng.mp3 90% of the Javadoc for this package is the phrase DOCUMENT ME!. They suck. I couldn't even figure out the API!
javazoom.spi The API is awkward for ID3 alone since this library is primarily for decoding MP3 audio. But it works perfectly as an ID3 tag reader! This is what I'm using, it's the best.
Moral of story: if you wanna use open source software, be prepared to waste 4 hours on crap. But when you do find that diamond in the rough, you will love it forever as if it were your poodle or wife.
# posted by Jesse Wilson
on Wednesday, April 13, 2005
14 comments
post a comment
JBoss 4 and JAAS
I've just wasted two days trying to figure out why my J2EE app's login wasn't working. Here's the EJB code:
/**
* Log in as this user.
*
* @ejb.interface-method
* view-type="local"
* @ejb.transaction
* type="Mandatory"
*/
public void tryLogin() throws LoginException {
logger.info("Logging in to " + LOGIN_DOMAIN + " as " + getId() + " with credentials \"" + getPassword() + "\"");
UsernamePasswordHandler handler = new UsernamePasswordHandler(getId(), getPassword());
LoginContext loginContext = new LoginContext(LOGIN_DOMAIN, handler);
loginContext.login();
}
In JBoss 3.2.7, this code logs in the current user so that they can run privileged methods. I can call EntityContext.getCallerPrincipal() and everything works great.
In JBoss 4, this code does absolutely nothing! It doesn't log in the user, it doesn't throw an exception and it doesn't provide any indication on why the user isn't getting logged in. EntityContext.getCallerPrincipal() returns my not-logged-in principal. Horrible!
If I find out what the heck is wrong in JBoss 4, I'll post an update.
# posted by Jesse Wilson
on Monday, April 11, 2005
1 comments
post a comment