Authentication - RESTful Web Services with Dropwizard (2014)

RESTful Web Services with Dropwizard (2014)

Chapter 9. Authentication

Authentication is the process of verifying that the user who is accessing an application is indeed who he/she claims to be and also, that he/she is allowed to access and use our application. In this chapter, we'll see how we can secure our web services with authentication mechanisms.

Building a basic HTTP authenticator

Our web service now has the functionality that allows anyone to use an HTTP client and create and retrieve contacts. We need to somehow secure our web service and authenticate the users that call it. The most common way of authentication is basic HTTP authentication, which requires a basic set of credentials: a username and password.

Getting ready

Before we proceed with securing our web service, we need to add the dropwizard-auth dependency to our project, adding the following to the dependencies section of our pom.xml file:

<dependency>

<groupId>io.dropwizard</groupId>

<artifactId>dropwizard-auth</artifactId>

<version>0.7.0-SNAPSHOT</version>

</dependency>

How to do it…

Let's see what it takes to build the authentication mechanism and secure our methods; perform the following steps:

1. Create a new class in the com.dwbook.phonebook package named PhonebookAuthenticator; here, we are going to build our service's security mechanism. The class needs to implement the Authenticator<C, P> interface and its #authenticate() method. The first parameter of the authenticator is the Authentication method, whereas the second one is the return type of the #authenticate() method.

2. package com.dwbook.phonebook;import com.google.common.base.Optional;

3. import io.dropwizard.auth.AuthenticationException;

4. import io.dropwizard.auth.Authenticator;

5. import io.dropwizard.auth.basic.BasicCredentials;

6. public class PhonebookAuthenticator implementsAuthenticator<BasicCredentials, Boolean> {

7. public Optional<Boolean> authenticate(BasicCredentials c) throws AuthenticationException {

8. if (c.getUsername().equals("john_doe") && c.getPassword().equals("secret")) {

9. return Optional.of(true);

10. }

11. return Optional.absent();

12. }

}

13. Enable the authenticator you've just built by adding it to the Dropwizard environment along with JerseyEnvironment#register(), passing to it a BasicAuthProvider instance. The constructor of BasicAuthProvider takes an instance of the authenticator to be used as the input and the authentication realm. You will also need to import io.dropwizard.auth.basic.BasicAuthProvider.

14. // Register the authenticator with the environment

15. e.jersey().register(new BasicAuthProvider<Boolean>(

new PhonebookAuthenticator(), "Web Service Realm"));

16. You may now secure web service endpoints, modifying the declarations of the ContactResource class' methods to expect a Boolean variable as the parameter, annotated with @Auth (import io.dropwizard.auth.Auth). The inclusion of this annotated parameter will trigger the authentication process.

17.public Response getContact(@PathParam("id") int id, @Auth Boolean isAuthenticated) { … }

18.

19.public Response createContact(Contact contact, @Auth Boolean isAuthenticated) throws URISyntaxException { … }

20.

21.public Response deleteContact(@PathParam("id") int id, @Auth Boolean isAuthenticated) { … }

public Response updateContact(@PathParam("id") int id, Contact contact, @Auth Boolean isAuthenticated) { … }

22. Build and start the application and then try to access any of the endpoints of the ContactResource class, such as http://localhost:8080/contact/1, trying to display the contact with an ID equal to 1. You will see a message stating that the server requires a username and a password.

How to do it…

How it works…

The dropwizard-auth module includes everything we need in order to secure our services. We just need to implement an Authenticator and register it with the Dropwizard environment.

Then, when we use the @Auth annotation for a method's input parameter, we indicate that the user who is accessing our service must be authenticated. Each time an HTTP request is performed on a method that contains a variable annotated with @Auth, the authentication provider intercepts it requesting a username and password. These credentials are then passed on to our authenticator who is responsible for determining whether they're valid or not. Whatever the authentication result is, that is, the return value of the#authenticate() method, it is injected in the variable that is annotated with @Auth. In case the authentication is unsuccessful or no credentials are provided, the request is blocked and the response is an HTTP/1.1 401 Unauthorized error. You can see the response received after performing an HTTP request with cURL without providing credentials in the following screenshot:

How it works…

Our authenticator class needs to be a class that implements the Authenticator<C, P> interface, where C is the set of credentials that we may use to authenticate the user and P is the type of the authentication's outcome. In our case, we used BasicCredentials as the credentials store, which is what BasicAuthProvider provides. In the #authenticate() method, we perform all the tasks required to authenticate the user. We implemented this to check that the user's name is john_doe as identified by the password, secret. This was an example; the next recipe illustrates how to authenticate users when their details (username and password) are stored in a database.

There's more…

As you may have noticed, our authenticator's #authenticate() method's return type is Optional. This is a Guava type that allows us to prevent null-pointer exceptions. There are cases where the #authenticate() method should return nothing, so instead of simply returning null (which could cause problems if not handled correctly), we return Optional.absent().

Such cases are when we need to provide an instance of the authenticated principal (that would probably contain username, name, e-mail, and so on) to the methods we secure, instead of just a boolean parameter, as we did in this example.

Setting client's credentials

We have secured our web service, in particular the endpoints of the ContactResource class. Our client needs to be updated as well in order to be able to access these protected resources.

To do so, we will need to modify the App#run() method. Use the #addFilter() method of the client object, right after its instantiation, adding HTTPBasicAuthFilter (import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter) and providing the correct username and password.

final Client client = new JerseyClientBuilder().using(environment).build();client.addFilter(new HTTPBasicAuthFilter("john_doe", "secret"));

The #addFilter() method is used to add additional processing instructions to the client object. That is, every request that is performed by our Jersey client has to be processed by the filters we've added before it is eventually performed. In this case, we use the#addFilter() method in order to add the appropriate BasicAuth headers to every outgoing HTTP request.

Optional authentication

There are many cases where authentication should be optional. Think of a service that returns personalized information for a user and a default message when no user is logged in. In order to declare optional authentication, we should have provided therequired=false parameter on the @Auth annotation, as shown in the following code:

@Auth(required=false)

Authentication schemes

We used basic HTTP authentication in our application; however, it is not the only available authentication scheme. For example, some web services use API key authentication. In such cases, the authenticator should be checking the headers of the HTTP request, verifying the validity of the transmitted API key. However, doing so would require the usage of a custom authentication provider as well. In any case, the use of an authentication method depends on your application's needs.

Authenticating users with credentials stored in a database

In the previous recipe, we used a hard-coded set of username and password to verify the users' identity. In most real-world cases though, you will need to identify users and verify their identity using credentials that are stored in a database, or more specifically, in a table that holds user information.

Getting ready

Let's first create a table in the database that will hold user data.

Start the MySQL client, and after logging in, execute the following query in the phonebook database:

CREATE TABLE IF NOT EXISTS `users` (

`username` varchar(20) NOT NULL,

`password` varchar(255) NOT NULL,

PRIMARY KEY (`username`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Now let's add a user to the database by running the following query:

INSERT INTO `users` VALUES ('wsuser', 'wspassword');

How to do it…

We are going to modify our authentication provider in order to check the current user's credentials in the database. Let's see how:

1. Since we are going to be interacting with the database for validating the user, we will need a DAO. So, create the UserDAO interface in the com.dwbook.phonebook.dao package.

2. package com.dwbook.phonebook.dao;

3. import org.skife.jdbi.v2.sqlobject.*;

4. public interface UserDAO {

5. @SqlQuery("select count(*) from users where username = :username and password = :password")

6. int getUser(@Bind("username") String username, @Bind("password") String password);

}

7. Modify PhonebookAuthenticator, adding a UserDAO instance as a member variable, creating a constructor to initialize the DAO instance using jdbi, and finally altering the authenticate method by utilizing the UserDAO instance for verifying user data by querying the database.

8. import org.skife.jdbi.v2.DBI;

9. import com.dwbook.phonebook.dao.UserDAO;

10. import com.google.common.base.Optional;

11. import io.dropwizard.auth.AuthenticationException;

12. import io.dropwizard.auth.Authenticator;

13. import io.dropwizard.auth.basic.BasicCredentials;

14.

15. public class PhonebookAuthenticator implements Authenticator<BasicCredentials, Boolean> {

16. private final UserDAO userDao;

17.

18. public PhonebookAuthenticator(DBI jdbi) {

19. userDao = jdbi.onDemand(UserDAO.class);

20. }

21.

22. public Optional<Boolean> authenticate(BasicCredentials c) throws AuthenticationException {

23. boolean validUser = (userDao.getUser(c.getUsername(), c.getPassword()) == 1);

24. if (validUser) {

25. return Optional.of(true);

26. }

27. return Optional.absent();

28. }

}

29. In the App#run() method, modify the registration of our authenticator in order to pass the existing jdbi instance to its constructor.

30.// Register the authenticator with the environment

31.e.jersey().register(new BasicAuthProvider<Boolean>(

new PhonebookAuthenticator(jdbi), "Web Service Realm"));

You may now rebuild, run, and test the application again. This time, when requested, you will need to provide the username and password set stored in the database instead of the hard-coded ones.

How it works…

Upon every request that is performed on a protected resource, our application checks the user's credentials against the database. To do so, we created a simple DAO with a single query that actually counts the rows that match the provided username and password. Of course, this could be either 0 (when the username/password set is incorrect) or 1 (when there is a correct set of credentials provided). This is what we check for in the authenticator's #authenticate() method.

There's more…

In this recipe, we stored the password in a database as plain text. This is normally not the appropriate way to do so; passwords should always be encrypted or hashed, and never stored in clear text, to minimize the impact of a possible intrusion or unauthorized access.

Caching

To improve our application's performance, we could cache the database credentials. Dropwizard provides the CachingAuthenticator class that we could use for this matter. The concept is simple; we build a wrapper around our authenticator with theCachingAuthenticator#wrap() method and register it with the environment. We will also be defining a set of caching directives, for example, how many entries to cache and for how long, using Guava's CacheBuilderSpec. For this example, we need to importio.dropwizard.auth.CachingAuthenticator and com.google.common.cache.CacheBuilderSpec.

// Authenticator, with caching support (CachingAuthenticator)

CachingAuthenticator<BasicCredentials, Boolean> authenticator = new CachingAuthenticator<BasicCredentials, Boolean>(

e.metrics(),

new PhonebookAuthenticator(jdbi),

CacheBuilderSpec.parse("maximumSize=10000, expireAfterAccess=10m"));

// Register the authenticator with the environment

e.jersey().register(new BasicAuthProvider<Boolean>(

authenticator, "Web Service Realm"));

// Register the authenticator with the environment

e.jersey().register(new BasicAuthProvider<Boolean>(

authenticator, "Web Service Realm"));

The key statement in the preceding snippet is CacheBuilderSpec.parse("maximumSize=10000, expireAfterAccess=10m"));. With this statement, we configure the wrapper to cache 10000 principals (the maximumSize property), that is, sets of usernames/passwords, and keep each of them cached for 10 minutes. The CacheBuilderSpec#parse() method is used to build a CacheBuilderSpec instance by parsing a string. This is for our convenience, allowing us to externalize the cache configuration, as instead of parsing a static string, we could parse a property defined in our configuration settings file.