Configure HTTP-auth with CXF and JAX-RS

Friday, September 14th 2012, 4:40 pm

JAX-RS is a great API to create RESTful services in Java. It simplifies the definition and implementation of any REST endpoint that you want to expose in your application. Because of its simplicity I have started to use it quite a bit in personal projects and I also use it in projects at my day job. My favorite implementation so far is CXF. CXF is very Spring friendly (which is a plus when using Spring extensively like me), and can be fully configured from Spring configuration files.

One hurdle that I had been facing recently, was that I wanted to set up HTTP Basic Auth for some endpoints in my JAX-RS application but not for all. The CXF documentation shows how to enable HTTP Basic Auth for all HTTP requests served by your JAX-RS application, so this is the approach that I first followed. I tried getting the request URI from the passed Message, to match which URLs needed to be authenticated and which ones did not, however this quickly got out of hand. The code started to look ugly with all kinds of:

String uri = m.get(Message.REQUEST_URI).toString();
if(uri.startsWith("/not_protected") {
    ...
} else if(uri.startsWith("/another_not_protected") {
    ...
}

I started to think of alternative solutions, and thought to myself, wouldn't it be great if I could just annotate my JAX-RS method with something like @Auth and have it automatically picked up by the filter? So I started poking around in CXF source code, and found out that the JAXRSInterceptor (the CXF interceptor in charge of doing all the JAX-RS magic), puts the selected JAX-RS method for any given request inside the message exchange. Specifically the interceptor puts it as an OperationResourceInfo.

Now that I was able to see which method was going to be called, I could just use Java reflection to see if the given method had the right annotations. The code to do the checking looks like this:

public class AuthenticationHandler implements RequestHandler {

    public Response handleRequest(Message m, ClassResourceInfo resourceClass) {

        if(!shouldAuthenticate(m)) {
            return null;
        }

        AuthorizationPolicy policy = (AuthorizationPolicy)m.get(AuthorizationPolicy.class);
        String username = policy.getUserName();
        String password = policy.getPassword(); 
        if (isAuthenticated(username, password)) {
            // let request to continue
            return null;
        } else {
            // authentication failed, request the authetication, add the realm name
            // if needed to the value of WWW-Authenticate 
            return Response.status(401).header("WWW-Authenticate", "Basic").build();
        }
    }

    public boolean shouldAuthenticate() {
        OperationResourceInfo info = m.getExchange().get(OperationResourceInfo.class);
        if (info != null) {
            return info.getAnnotatedMethod().isAnnotationPresent(Auth.class);
        } else {
            return false;
        }
    }

}

And the Auth annotation looks like this:

@Inherited
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {

}

Now all I had to do was annotate my method with @Auth, and it would be authenticated. Like so:

@Auth
@GET
public Response authenticated() {
    return Response.ok().build();
}

With this I could be sure that my JAX-RS method would be protected with HTTP Basic Auth. This took out quite a bit of code from the original source and centralized the knowledge of which end points to authenticate and which not to authenticate right along with the actual method being called, which makes thinking about the logic easier.

comments powered byDisqus