Authentication with Jersey 2
When creating various web services such as a REST service authentication tends to become relevant sooner or later. In some cases you want to integrate into existing services such as Google+ using OAuth in other cases – like here – you would rather have your own security schemes.
In searching for ways to do this I ran into a lot of older articles, which although being useful, often provided slightly outdated information or only half examples. This aims to provide a fully working basic example, with a complete source code for assistance. Basic example here means just enough to prove and show the concepts and enough to be used as a start for more advanced code.
WARNING: If you develop your own security scheme like here, you take over the responsibility of the safety of your system. Do make sure to have a relatively secure scheme (this is just a basic example to get you started), and do make sure to keep up to date with security issues. You have been warned.
Basic framework
The code used in this guide has the following basic traits. The directory structure of the project follows the common Java EE structure, such that: “/src” contains the Java source, “/lib” will contain the libraries (once ant + ivy has been run), “/web” will contain the basic html, css and JavaScript, “/web/WEB-INF” contains web.xml with the basic configurations and finally “/out” will contain the output from compilation.
More interestingly “/src” contains the Java source, divided into the following packages:
- dk.rohdef.jerseyauth – contains MockDao a simple data access object to enable testing, this only contains mock data and should be replaced with real data, such as from a database, ldap or similar
- dk.rohdef.jerseyauth.model – models for the project, basically a User, Group, Session and LoginRequest. Representing a user of the system, a user group (such as admin or user), a login session and a request to login.
- dk.rohdef.jerseyauth.rest – the services for this demo, Login for logging the user in and Service for testing the permissions
- dk.rohdef.jerseyauth.jersey – the configuration class for checking the session, we’ll create that in a moment
In the project there’s an ant build script and an ivy dependency file. Run “ant resolve” to ensure you have the libraries you need, you may need to install ivy first. You can also play with the live demonstration running on tomcat built using “ant build-war”.
Login service
The login service is fairly straight forward. It provides two methods, login and logout. The login method takes a LoginRequest and returns a Session on a successful login, if the login fails it returns null. The logout method here is just a dummy, but in real usage it should at a minimum revoke the users access token.
One thing to note here, this design is chosen to prevent passing passwords along. Following this structure you only need a password when logging in.
If your Jersey is set up to look for services at your version of the login service, you can take it for a spin already. Jump to the testing section and take login for a spin, before continuing with the rest. Can you get a session token?
Jersey support files
This part is mostly built upon the same template as in Deisss’ article Jersey (JAX-RS) implements a HTTP Basic Auth decoder, but rewritten to more recent structures. Thanks to Deisss for getting me started on this.
The first class in this is BasicAuth taken directly from the article above. This in short terms decodes the authentication header we’re going to send along from our web application. The string will usually look something like: “Basic Zm9vOmJhcg==” where “Zm9vOmJhcg==” in this example is an encoding of “foo:bar”, BasicAuth just decodes this.
RfSecurityContext is the one the security framework asks to find out what to permit and not. In this example it is very primitive as it just gives the user name for the user and verifies if the user is in a specific role or not.
Finally AuthFilter is the glue making the magic happen. This is implements ContainerRequestFilter enabling it to filter incoming requests to the services. Here we grab the “authorization” header, decode it and validate it. If an active session is found the security context is set, so our permission annotations (we will implement them in a moment) can be used.
String auth = requestContext.getHeaders() .getFirst("authorization"); if(auth == null) return; String[] loginAndToken = BasicAuth.decode(auth); // ... Code for checking the session omitted for brevity, // see GitHub for full example SecurityContext securityContext = new RfSecurityContext(session); requestContext.setSecurityContext(securityContext);
Putting it together
Almost there, stay with me a little longer. Before we can test our precious system, we need to tell Jersey about it. Basically we need a few configurations in “web/WEB-INF/web.xml”. There’s three things that Jersey wants to know. First one is where the services are, this one you probably already have set up in your own project.
The AuthFilter created in the previous step needs to be added – that can be done using both the packages configuration as used for the services and by adding a class name specifically. Here a class name is used since we have only file that Jersey needs to know about in that folder, but if you have a lot of filters etc. in the same folder, using packages may be an advantage.
Lastly Jersey needs to know that we’re using roles, so it has to load: org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature, which is loaded through the class names parameter, just like we did with AuthFilter. Thus you need to add the following to your web.xml in the part where you configured the servlet.
<init-param> <param-name> jersey.config.server.provider.classnames </param-name> <param-value> dk.rohdef.jerseyauth.jersey.AuthFilter, org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature </param-value> </init-param>
Alternatively it can be configured using resource configuration.
Securing a service
And the last piece of the puzzle, now we have a way to login, a way to read session tokens and Jersey knows about it. Now let’s create a service and secure it. Securing is done really easy. If you already have a Jersey service all you need to do is to add some annotations and you’re done. There is two annotations “@PermitAll” and “@RolesAllowed(roles)”, where roles is a comma separated string of roles.
That’s it, put them in front of your service class or methods to provide more or less specific rules. Take a look at Login.java and Service.java for examples.
Running the services
In most of this part I’m assuming that you run jQuery to do your requests. Transforming this setup to other setups should be really easy as long as you can use json and set the headers. The important parts to bring along is the following headers “Accept”, “Content-Type”, “Method” and for the secured calls “Authorization”. And of course you need to be able to provide the payload.
Note that in the code provided there’s a working example, so go to the “/web” folder to get inspiration.
Basic login
The basic structure is really easy, for jQuery just set up the settings as follows:
var loginRequest = { username: null, password: null }; var successCallback = function(data) { / ... / }; var errorCallback = function(jqXHR, status, text) { / ... / }; var loginSettings = { method: "POST", contentType: "application/json; charset=utf-8", url: "./rest/auth/login", dataType: "json", data: JSON.stringify(loginRequest), success: successCallback, error: errorCallback }
And run it using “$.ajax(loginSettings);”. Of course, set the username and password to something sensible and use some sensible code in the callbacks. You can usually verify calls like this in your browsers development tools.
Important things to notice are:, ensure that method, contentType and dataType is set correctly, and ensure that your data matches the expected payload. It’s worth recalling that Jersey is case sensitive on the payload.
Using the session
Here I assume the session is stored on “window.session” (that is in the global scope) – if you use a framework, do check if it provides better ways of storing the token or store it in an application local variable, be careful with using global variables, unless it’s a toy example such as my demonstration.
To use the session is almost as easy as logging in, yay Assuming that the callbacks have been overwritten with meaningful callbacks this is all you need:
var encodedSession = btoa(session.username+":" +session.token); var loginSettings = { method: "POST", headers: { Authorization: "Basic " + encodedSession }, contentType: "application/json; charset=utf-8", url: "./rest/service/funny", dataType: "json", success: successCallback, error: errorCallback }
That’s it really. Just call it using $.ajax again and you’re good to go.
Where to from here
As mentioned in the start, this is example is a basic working example. You probably noticed that I do provide suggestions during the guide, because this still leaves the majority of work up to you.
- Do ensure encryption on the system! This login system does in no way prevent man in the middle or similar, the token setup makes leaks harder to happen and can prevent the worst damage of a leaked password.
- Do check up on know security risks etc. Security is hard, and there’s a lot to consider. But following even simple guidelines can make your life much easier. Consider taking a look on ESAPI from OWASP, I haven’t checked it yet, but usually OWASP is a good source for security related material.
- Consider the relevant security schemes. Here I just check group membership. Hey perhaps that’s just what you need and good for you. Perhaps you need more sophisticated schemes where multiple groups can have access, new groups can be created and removed, then you may need to think roles in terms of what they should have access to.
- In the code here failures just return null. A potential improvement is using http status codes to mark failures or using exceptions.
- Just one word of advice. Remember YAGNI – you ain’t gonna need it – in short do not over design, make sure that your security can what you need, but do not do much more before you need it. Otherwise you’ll end up spending a lot of time creating features that won’t be used.
Hopefully this got you started safe an sound on making your own security work.
Comments
Display comments as Linear | Threaded