Wednesday, October 15, 2008

why the hell didn't I ever try this before

For years and years (like since approximately 2001) I've been bitching and complaining, and never doing anything in particular, about customizing HTTP basic authentication in a servlet container. It's container specific, it involves lots of XML, and it usually involves sticking your classes up into the appserver's backside.

I've never actually gotten it to work. Then again, I haven't really tried all that hard either. It's just always seemed like more work than it's worth, especially when compared to things like Restlet and even Sun's own com.sun.net.httpserver. Anyway, I never did it. Instead I did this:

Just write a servlet filter that does the authentication and sets up the request appropriately. Duh. Why didn't they just include a class like this in the Servlet API instead of all the shenanigans?

NOTE: Yes, I know basic authentication is insecure. Prove to me that it's somehow less secure than an HTML form sending the credentials in plain text.


import java.io.IOException;
import java.security.Principal;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.codec.binary.Base64;

public class BasicAuthServletFilter implements Filter {

private SessionCache sessions = new SessionCache();


public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) arg0;
HttpServletResponse httpResponse = (HttpServletResponse) arg1;

String auth = httpRequest.getHeader("Authorization");

if( auth == null ) {
send401(httpResponse);
return;
}

String[] tokens = auth.split(" ");

if( !tokens[0].equalsIgnoreCase("Basic")) {
send401(httpResponse);
return;
}

String[] credentials = new String( Base64.decodeBase64(tokens[1].getBytes()) ).split(":");


try {
customLogin( credentials[0], credentials[1] );
} catch (Exception e) {
send401(httpResponse);
return;
}

// implementation of MyPrincipal is left an exercise for the reader
final java.security.Principal p = new MyPrincipal(credentials[0]);

HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(httpRequest) {

@Override
public String getAuthType() {
return HttpServletRequest.BASIC_AUTH;
}

@Override
public String getRemoteUser() {
return p.getName();
}

@Override
public Principal getUserPrincipal() {
return p;
}

@Override
public boolean isUserInRole(String role) {
// probably want to customize here
return true;
}
};


arg2.doFilter(wrapper, arg1);

}

private void send401(HttpServletResponse httpResponse) throws IOException {
httpResponse.setHeader("WWW-Authenticate",
"Basic realm=\"My Realm\"");
httpResponse.sendError(401);
}

public void customLogin(String user, String password) throws Exception {

// proprietary business logic (TM)

}


public void init(FilterConfig arg0) throws ServletException {
}


public void destroy() {
}




}

Comments: