Jersey (JAX-RS) implements a HTTP Basic Auth decoder

I recently play with a pretty nifty framework called Jersey, allowing to make REST application using annotations under Java.

This framework (and other JAX-RS implementation) is a pretty well done framework, quite easy to use, and pretty interesting feature inside.

Note: an updated version for Jersey 2.x is available here

I will describe here a HTTP Basic Auth using Jersey system. The HTTP Basic Auth allow to join, on each request, some login/password information to allow/disallow resources, regarding that authentification.

If you follow the REST rules, you already know that REST application are stateless, which means that you must NEVER have a login phase before accessing resources : all login you have to do has to be done within the request (basically we will set a special HTTP header for that).
In this case, Basic Auth is a usefull feature from HTTP protocol for doing that.

I will describe here the Basic authentification, not the digest one.

HTTP Basic Auth, principle

Basic Auth is a parameter you can add to an HTTP request, this looks like this :
Authorization: Basic ZGVwb3NlY2xpZW50OmRlcG9zZWNsaWVudA==

The Basic describe which method the client use (here Basic), and the code after is an encoded Base64 string « login:password ». Here the login deposeclient and password is also deposeclient.

Jersey, working with filter

Jersey offer a way to catch request (all of them), by making a filter. I will show here the filter for two products : The built in Java server, and Tomcat :

Java built-in server
ResourceConfig rc = new PackagesResourceConfig("");
rc.getProperties().put(
	"com.sun.jersey.spi.container.ContainerRequestFilters",
	"com.sun.jersey.api.container.filter.LoggingFilter;com.myprogram.AuthFilter"
);

HttpServer server = HttpServerFactory.create("http://localhost:9999/", rc);
server.start();
Tomcat
<init-param>
	<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
	<param-value>com.sun.jersey.api.container.filter.LoggingFilter;com.myprogram.AuthFilter</param-value>
</init-param>

Both Tomcat and Java built in allow same behaviour, here we add two filter into the Jersey process : the first one is a « official » one provided by Jersey, allowing to log all entry request. The second is our filter processing the Auth.

Now they are both added to Jersey process, we can start to make the Auth process.

Decode Auth

So, from now user will request our service using Basic Auth, we still have to process it :

/**
 * Allow to encode/decode the authentification
 * @author Deisss (LGPLv3)
 */
public class BasicAuth {
	/**
	 * Decode the basic auth and convert it to array login/password
	 * @param auth The string encoded authentification
	 * @return The login (case 0), the password (case 1)
	 */
	public static String[] decode(String auth) {
		//Replacing "Basic THE_BASE_64" to "THE_BASE_64" directly
		auth = auth.replaceFirst("[B|b]asic ", "");

		//Decode the Base64 into byte[]
		byte[] decodedBytes = DatatypeConverter.parseBase64Binary(auth);

		//If the decode fails in any case
		if(decodedBytes == null || decodedBytes.length == 0){
			return null;
		}

		//Now we can convert the byte[] into a splitted array :
		//	- the first one is login,
		//	- the second one password
		return new String(decodedBytes).split(":", 2);
	}
}

The DatatypeConverter comes from javax.xml.bind.DatatypeConverter

This static function take the « Basic ZGVwb3NlY2xpZW50OmRlcG9zZWNsaWVudA== » part, remove the « Basic  » and process the other part as a Base64 encoded, then separate login and password, return an array of both (login at position 0, password at position 1).

Now we get a class to separate everything, we need to use it in our filter.

Jersey Filter

Jersey filter take the request (or the response if it’s an output filter), and allow you to check absolutely everything inside, you can also add/remove/change properties into it, and refuse request if you want. We will use some of them to proceed here :

/**
 * Jersey HTTP Basic Auth filter
 * @author Deisss (LGPLv3)
 */
public class AuthFilter implements ContainerRequestFilter {
	/**
	 * Apply the filter : check input request, validate or not with user auth
	 * @param containerRequest The request from Tomcat server
	 */
	@Override
	public ContainerRequest filter(ContainerRequest containerRequest) throws WebApplicationException {
		//GET, POST, PUT, DELETE, ...
		String method = containerRequest.getMethod();
		// myresource/get/56bCA for example
		String path = containerRequest.getPath(true);

		//We do allow wadl to be retrieve
		if(method.equals("GET") && (path.equals("application.wadl") || path.equals("application.wadl/xsd0.xsd")){
			return containerRequest;
		}

		//Get the authentification passed in HTTP headers parameters
		String auth = containerRequest.getHeaderValue("authorization");

		//If the user does not have the right (does not provide any HTTP Basic Auth)
		if(auth == null){
			throw new WebApplicationException(Status.UNAUTHORIZED);
		}

		//lap : loginAndPassword
		String[] lap = BasicAuth.decode(auth);

		//If login or password fail
		if(lap == null || lap.length != 2){
			throw new WebApplicationException(Status.UNAUTHORIZED);
		}

		//DO YOUR DATABASE CHECK HERE (replace that line behind)...
		User authentificationResult =  AuthentificationThirdParty.authentification(lap[0], lap[1]);

		//Our system refuse login and password
		if(authentificationResult == null){
			throw new WebApplicationException(Status.UNAUTHORIZED);
		}

		//TODO : HERE YOU SHOULD ADD PARAMETER TO REQUEST, TO REMEMBER USER ON YOUR REST SERVICE...

		return containerRequest;
	}
}

Quite long, but quite efficient code :

  • If you have some request which must pass authentification, you should put them next to « application.wadl« 
  • The authentificationThirdParty is a class I invent here to move the login/password check outside that class, you can of course put what you want (I would suggest here to add resource and method to make a kind of ACL control here)
  • Also, at the end I encourage you to add the retrieve user inside the request, to be able to retrieve it after the filter inside the REST app. Here is a the line you should put for Tomcat : « sr.setAttribute(« logged_user », authentificationResult);« , while sr is « @Context HttpServletRequest sr; » outside the function (inside class somewhere)

Final words

You can now easily protect your REST webservice with an authentification feature. But this does not resolve everything, you should add here some HTTPS, because Basic Auth is unsecure (a simple Base64 decode give the full login and password uncrypted). Use this carefully so !

Publicités

13 Commentaires

  1. Thanks for this wonderful post!! Very helpful!

  2. benjiman

    Very helpful! Thank you!

  3. Hey thanks for the post, it worked perfectly and was super easy to implement.
    FOLLOW-UP Question: how do i trigger a http-auth prompt window if i access the API through a browser? Right now it just gives me a 401 (which is expected)

  4. Alok Verma

    Great Tutorial… It helped me a lot 🙂

  5. houssemzaier

    Hi, it didin’t work with me I am workin on Glassfish4 with JAXRS 2.0.
    Have you any idea about that?

    • deisss

      Simply because it´s Jersey 1.x compatible only (and most of documentation you will found is for 1.x branch). Lucky for you i’m soon to update those articles for 2.x branch !

  6. Celulu

    Hi, deiss!
    this tuto helps me a lot. But I work with jersey 2.9. Please tell me, where could I find more information for adapting it with this new version. I’ve got problems with the web.xml syntax.
    thx

  7. IJR

    I saw that when you secure a Jersey WS by web.xml moves from use Basic Auth over http to httpS is straightforward, you have to add:

    CONFIDENTIAL

    If I don’t use roles, I have to do programatically by a filter like you do, but how change that filter to use httpS?

  8. Amit

    Very Nice explanation , was searching the implementation of Basic Auth from the last 2 weeks , finally got it working . Thanks a ton man.

  9. Sri

    Thanks for the wonderful post.. it worked like a charm with Jersey1.2, jackson1.9.2 libraries.

  10. Pingback: Authentication with Jersey 2 | Rohde Fischer

  11. Jebus

    thank you. It s working.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :