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

http://localhost:8080/employees

http://localhost:8080/employees/1

Drop me your questions in the comments section.
Happy Learning !!
 
					 
Comments