Dropwizard Authentication and Authorization

Using dropwizard, we have learned about creating REST APIs, writing client code and adding health-check filters. In this tutorial, we will learn to add username/password based authentication and role based authorization capabilities into REST APIs using basic authentication.

1. Maven

Authentication capabilities are added as a separate module in the dropwizard application.

<dependency>
  <groupId>io.dropwizard</groupId>
  <artifactId>dropwizard-auth</artifactId>
  <version>4.0.0</version>
</dependency>

2. Security Principal

In security, the principal object represents the user for which credentials have been supplied with the request. It implements java.security.Principal interface.

package com.howtodoinjava.app.auth;

import java.security.Principal;
import java.util.Set;

public class User implements Principal {

  private final String name;
  private final Set<String> roles;

  public User(String name) {
    this.name = name;
    this.roles = null;
  }

  public User(String name, Set<String> roles) {
    this.name = name;
    this.roles = roles;
  }

  public String getName() {
    return name;
  }

  public int getId() {
    return (int) (Math.random() * 100);
  }

  public Set<String> getRoles() {
    return roles;
  }
}

3. Custom Authenticator

Authenticator class is responsible for verifying username/password credentials included in Basic Auth header. In enterprise applications, you may fetch the user’s password from the database, and if it matches then you set the user roles into a principal object. In dropwizard, you will need to implement io.dropwizard.auth.Authenticator interface to put your application logic.

package com.howtodoinjava.app.auth;

import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;

import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

public class AppBasicAuthenticator implements Authenticator<BasicCredentials, User> {

  private static final Map<String, Set<String>> VALID_USERS = ImmutableMap.of(
      "guest", ImmutableSet.of(),
      "user", ImmutableSet.of("USER"),
      "admin", ImmutableSet.of("ADMIN", "USER")
  );

  @Override
  public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
    if (VALID_USERS.containsKey(credentials.getUsername()) 
        && "password".equals(credentials.getPassword())) {
      return Optional.of(new User(credentials.getUsername(), VALID_USERS.get(credentials.getUsername())));
    }
    return Optional.empty();
  }
}

4. Custom Authorizer

Authorizer class is responsible for matching the roles and deciding whether the user is allowed to perform a certain action or not.

package com.howtodoinjava.app.auth;

import io.dropwizard.auth.Authorizer;
import jakarta.ws.rs.container.ContainerRequestContext;
import org.checkerframework.checker.nullness.qual.Nullable;

public class AppAuthorizer implements Authorizer<User> {

  @Override
  public boolean authorize(User user, String role,
      @Nullable ContainerRequestContext containerRequestContext) {
    return user.getRoles() != null && user.getRoles().contains(role);
  }
}

5. Configure BasicCredentialAuthFilter

Now let’s register our custom classes into the dropwizard security framework.

public class App extends Application<ApplicationConfiguration> {

  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

  @Override
  public void initialize(Bootstrap<ApplicationConfiguration> b) {
  }

  @Override
  public void run(ApplicationConfiguration c, Environment e) throws Exception {

    //...

    //****** Dropwizard security ***********/
    e.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<User>()
        .setAuthenticator(new AppBasicAuthenticator())
        .setAuthorizer(new AppAuthorizer())
        .setRealm("BASIC-AUTH-REALM")
        .buildAuthFilter()));
    e.jersey().register(RolesAllowedDynamicFeature.class);
    e.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
  }

  public static void main(String[] args) throws Exception {
    new App().run(args);
  }
}

6. Securing REST APIs with Annotations

6.1. @Auth to Trigger Authentication

Adding @Auth annotation will trigger an authentication filter on any API where it is put as a parameter.

@GET
@PermitAll
public Response getEmployees(@Auth User user) {

  return Response.ok(repository.getEmployees()).build();
}

6.2. Jakarta Security Annotation for Authorization

The jakarta security annotations can be used to configure what is allowed to a user having a specific role or not.

@GET
@Path("/{id}")
@PermitAll
public Response getEmployeeById(@PathParam("id") Integer id, @Auth User user) {

  //...
}

@DELETE
@Path("/{id}")
@RolesAllowed({"ADMIN"})
public Response removeEmployeeById(@PathParam("id") Integer id, @Auth User user) {

  //...
}

In this way, you can add various authentication schemes in all your APIs as needed.

7. Demo

Let’s test out our secured APIs.

Call Any secured API

Basic Authentication Screen
Basic Authentication Screen

http://localhost:8080/employees

Authenticated and allowed to all roles
Authenticated and allowed to all roles

http://localhost:8080/employees/1

Authenticated and allowed to ADMIN role only
Authenticated and allowed to ADMIN role only

Drop me your questions in the comments section.

Happy Learning !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.