REST Jersey2 JSON JWT Authentication Authorization

This tutorial explains how to create a Java REST Web Service with Jersey2, JSON communication, JSON Web Token authentication and role authorization using annotations and request filters. Passwords are hashed with PKDF2 and salted with HMAC SHA1. The provided code is working with two tested databases, OrientDB and SQLite. The data access layer uses the DAO (Data Access Object) pattern, in order to separate business logic from the database layer. This tutorial is a rather long one, since there are a lot of open topics to go into and explain. We hope you got some time.

The code is available at Github.

1. Prerequisites

  1. Maven
  2. Tomcat (we use 8.5 but any Version starting with 7 should be working – Java8)
  3. Curl or equivalent (we use Postman to query the rest service)

2. Project setup

We created a maven-archetype-webapp project in Eclipse which provides the project structure we need. You will get the same result when executing mvn install. This generates a WAR file that can be deployed on a Tomcat Server.

The key dependencies for the REST web service are:

  • Jersey2 (Glassfish)
  • (Jackson) JAX-RS
  • Jose4j JWT implementation

The architecture of the web application looks like this:

REST app architecture

We use JSON-based communication between the client and the server. All requests are filtered before they reach the REST API in order to check for authentication (JWT) and authorization (roles like Admin, User etc.).

The Business Logic layer does not exist, we access the Data Access Layer directly from the REST API for simplification.

Finally we provide two implementations as persistent storage. Multi-model store OrientDB and SQLlite. Both databases have in-memory and file persistence, so you do not have to install extra software.

3. Register Authentication Filter

Let us start from top to bottom. Requests from clients are filtered via an Authentication filter. We register a ResourceConfig class in the web.xml to apply our filter later on and specify our REST Web Service class.

The ResourceConfig registers our Authentication filter:

The Authentication filter implementation:

This method from the Authentication filter is called on every request to our server.

  1. We start of with checking access permissions for the required REST service via annotation (Line 6):
    • @PermitAll – e.g. everybody can access, like register or login / authenticate
    • @DenyAll – no one can use that method
    • @RolesAllowed({“admin”,”user”}) – only users with roles admin or user can call this method
  2. If the method annotation is different from @PermitAll, which means we do not have to check for authentication nor authorization, we continue (Line 6)
  3. Check if the called method has a @DenyAll annotation, which means no one has currently access and return a “forbidden” message (Line 9)
  4. Now we have a user calling an accessible function where we need some authentication. We check the JSON Web Token provided in the header (x-access-token) (Line 22)
  5. If we have a x-access-token  provided, we have to decode it and validate it (not expired, issuer etc correct) (Line 36)
  6. If the token is valid, we check if the provided (user) ID information matches a user in the database (Line 49)
  7. We cross check the token with an already available token in the database (Line 62)
  8. Finally we check if the user fulfills the role requirements (role stored in database, role for method in annotation) (Line 71,78)

We only proceed to the REST Web Service if no early abort via requestContext.abortWith(..) was executed.

4. REST Web Service

If we reach the REST Web Service, authentication and authorization are already processed in the authentication filter. This is where we start the interaction with the Business Layer (or in this example directly with the Data Base Layer):

This Web Service offers standard CRUD operations as well as an authentication method (send email and password to retrieve a token). The getAll method is only accessible from an admin (compare role annotations). We do not need any JSON annotation properties. Jackson provides a JSON deserializer and automatically matches attributes into the (User) POJO object. In order to do so the created POJOs need getter and setter methods for all attributes as well as an empty constructor.

The class itself has two annotations to define the allowed roles and determine the Web Service path:

Followed by the first Web Service method with several annotations

With these annotations we define a POST method which is accessible at …/user/create. Everybody may access this service (e.g. no authentication or authorization required). This methods consumes a JSON object like:

The method produces a JSON object as response.

Now let us have a look at a method that is only accessible by an admin user:

This method is a GET Method. We require a valid JWT which is checked in the Authentication filter and carries the user Id (data base reference) in the JWT body. Only admin users are allowed to access this method (checked in Authentication filter as well) and it produces JSON objects.

All the methods use exception propagation which is required to send only the necessary information back to the client (or possible attackers):

Here we catch a UserNotFoundException which is thrown whenever a user is not found in the database. Additionally we catch any other Exception and return status Unauthorized (Internal Server Error would be an option, but it looks worse 🙂 ). Therefore we avoid that critical Exceptions or even stack traces reach the client.

We decode the user ID (database ID) in the JWT body. Whenever we receive a valid JWT, we have an identification for the user sending that request.

In order to send only information from POJOs that we want the client to receive, we implemented a JsonSerializable interface with one method toJson(). Consequently, when returning a user object to the client, we do not want to return a password:

5. Data Access Layer

Now finished with the Web Service, we move on to the Data Access Layer. We use Data Access Objects (DAO) in order to abstract the Business Layer from the Data Access Layer. The idea behind DAO is to have an interface and a factory that provides the implementation for the database.

The UserDAO interface offers all methods we use in the REST Web Service. We have a config file that specifies the required database:

The UserDAOFactory produces an implementation of the UserDAO interface, depending on the database selection (SQLlite, OrientDB):

You can compare the usage in the REST Web Service. We only access the DAO object via the factory and do not care about the underlying database implementation. The DAO objects require a connection to a database, which is returned by the ConnectionFactory:

This factory returns an Connection Interface:

This interface is used to abstract the connection. E.g. we could use a PostgreSQL instead of SQLite connection that works with the SqlUserDAO (if the SQL syntax is equal).

6. Test the REST Web Service

Build the project and deploy it to a Tomcat server. If you have the project up and running it is time for some tests. We use Postman to send GET/POST requests to the server ( e.g. http://localhost:8080/rest-jersey2-json-jwt/user/create):

The Github repository includes a postman_collection.json file that offers some test cases.

If you do not use Postman:

  1. Start of with user creation ( firstname, lastname, email, password )
  2. If successful, the server will automatically log you in and return a x-access-token
  3. In the request header you need a field x-access-token which contains the token provided in step 2 (if no @PermitAll annotation available)
  4. Whenever you send a request with a JSON Body, make sure to include a header field Content-Type with value application/json

7. Improvements

I did not perform a lot of tests, the basic functionality works, but i would not consider it safe yet. I hope to do some more tests and updates when i have time. Some improvement ideas:

  • Check the incoming JSON input. There are no checks for the incoming JSON objects. We have no abstraction and directly load the JSON objects into our POJO files. This might be a security risk.
  • Introduce a Business Layer. Right now we work directly with the Data Access Layer in the Web Service. It would be better to add another abstraction layer to abstract the Web Service from the Data Layer.
  • JWT and Password encryption abstraction: Right now we use “hard”-coded implementations for JWT and encryption. It would be better maintainable if we abstract the algorithms via interfaces and factories as well. Some algorithms are considered safe now but become obsolete in a few years. The ability to change the underlying algorithm as fast as possible may come in handy (requires another field for versioning and dealing with old data).
  • Introduce a refresh token in the database.
  • Create an IP logger for authentication requests to avoid brute-force attacks. Basically you store which IP tried to login and deny the access after 3 failed tries in a row for some time.
  • Logout method. Create a method to invalidate the JWT which equals a logout.

Finally, to use the JWT authentication safely in production, a SSL connection is absolutely required (the JWT is not encrypted, only signed – Man in the middle attack).

This was a long tutorial, i hope it was still clear and understandable. I left out many parts of the code for simplification, i suggest you download the code in the beginning, have a look and come back here for some understanding.

If you have exceptions, questions or problems, feel free to ask and comment.

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply