Skip to content

How to Use

Benton Porter edited this page Jul 8, 2014 · 20 revisions

This page shows how to use jersey-hmac-auth on the server to secure your API and also in client libraries that you build to interface with the API.

Server

Maven Setup

To implement jersey-hmac-auth on the server, add this dependency to your application:

<dependency>
    <groupId>com.bazaarvoice.auth</groupId>
    <artifactId>jersey-hmac-auth-server</artifactId>
    <version>${version}</version>
</dependency>

Securing Resource Methods

You can secure your API endpoints by adding the @HmacAuth annotation to their corresponding Jersey resource methods. For example:

@Path("/pizza")
@Produces(MediaType.TEXT_PLAIN)
public class PizzaResource {
    @GET
    public String get(@HmacAuth Principal principal) {
        // This gets control only if the request is authenticated. 
        // The principal identifies the API caller (and can be of any type you want).    
    }
}

If a caller makes an HTTP GET request to the /pizza endpoint, then by default the resource method will get control only if the request is authenticated successfully. If authentication fails, then jersey-hmac-auth will automatically reject the request and return a "401 (Unauthorized)" HTTP status code before this method gets control.

You can control the default authentication behavior (e.g. to make authentication optional) by using a non-default request handler. There are more details about request handlers below.

Authenticators

Requests are authenticated on the server by an Authenticator class that you provide.

The authenticator is responsible for authenticating each request. It is supplied the credentials from the request (e.g. the API key, the signature provided by the caller, and other relevant parameters). It authenticates the request and returns a "principal" object that represents the authenticated API caller. The jersey-hmac-auth library will then inject the returned principal into the Jersey resource method that gets control to process the request.

The following is an example authenticator:

public class MyAuthenticator extends AbstractCachingAuthenticator<Principal> {
    // some code is intentionally missing 
    
    @Override
    protected Principal loadPrincipal(Credentials credentials) {
        // return the principal identified by the credentials from the API request
    } 

    @Override
    protected String getSecretKeyFromPrincipal(Principal principal) {
        // return the secret key for the given principal
    }
}

The jersey-hmac-auth library provides abstract authenticator classes that you can extend for your own use. The following covers them in detail. However, note that if you need something more custom, you can create your own authenticator by implementing the Authenticator interface.

AbstractAuthenticator

The AbstractAuthenticator class provides the following features:

  • Validates the API key by making sure that it identifies a non-null principal.
  • Validates the signature provided by the caller by generating a new signature and comparing it to the one on the request. If they match, then the signature is considered valid. This means that the caller had a valid secret key (they are a trusted source) and that the request has not been tampered with after being sent.
  • Validates the request timestamp. This ensures that the timestamp does not fall outside of an allowed time range, which is used to reduce the window of time for which a replay attack can occur.

AbstractCachingAuthenticator

The AbstractCachingAuthenticator class provides the same features as AbstractAuthenticator and also:

  • Caches the principal in memory and only loads the principal if it is not in the cache. This is especially helpful if your authenticator retrieves principals by querying another system, such as a database or another web service.

Request Handlers

Requests are handled by a RequestHandler class that controls how authentication is enforced on the server. Request handlers are responsible for decoding requests (e.g. parsing the API key, signature, and other relevant parameters from the request) and invoking an authenticator to perform the actual authentication. The request handler can then enforce the results however it wants.

The jersey-hmac-auth library provides some request handler classes that should be applicable for most applications. The following covers them in more detail. However, note that if you need something more custom, you can create your own request handler by implementing the RequestHandler interface.

DefaultRequestHandler

The DefaultRequestHandler class enforces authentication strictly. If a Jersey resource method is annotated with @HmacAuth, then it will only get control if the request is authenticated successfully. If it is not, then the resource method will not get control and instead the API caller will receive a "401 (Unauthorized)" status code.

OptionalRequestHandler

The OptionalRequestHandler class enforces authentication less strictly. If a request specifies authentication parameters (e.g. API key, signature, etc.), then the request handler will authenticate the request (and only allow the request to proceed if the request is authenticated successfully). However, if there are no authentication parameters on the request, then it will bypass authentication and allow the request to proceed. In this case, the Jersey resource method will just receive a null principal object.

This can be useful, for example, when first implementing jersey-hmac-auth in an existing application. In this case, you can deploy it in an "optional" state so that you can then start giving keys to your API callers. Once all API callers have keys and are building requests such that they can be authenticated, then you can deploy the API with the DefaultRequestHandler to enforce authentication strictly.

Registering your Authenticator with Jersey

Todo...


  1. Register the authentication provider with Jersey.

If using Dropwizard:

environment.addProvider(new HmacAuthProvider(new DefaultRequestHandler(new MyAuthenticator())));

If using straight Jersey, you basically do the same, but add the HmacAuthProvider to your Jersey environment.

Both implementations require specifying (or implementing your own) RequestHandler. There are three RequestHandlers provided for use:

* [DefaultRequestHandler](server/src/main/java/com/bazaarvoice/auth/hmac/server/PassThroughRequestHandler.java) - for general use, requires all requests to include proper authentication
* [OptionalRequestHandler](server/src/main/java/com/bazaarvoice/auth/hmac/server/OptionalRequestHandler.java) - relaxed, does not require authentication, but will authenticate if credentials are provided
* [PassThroughRequestHandler](server/src/main/java/com/bazaarvoice/auth/hmac/server/PassThroughRequestHandler.java) - for testing, simply returns the Principal passed in

Clients

The following clients are available, but note that you can write your own in any language as long as it follows the appropriate contract.

Java Client

To implement a Java client (using Jersey) that constructs requests encoded for HMAC authentication:

(1) Add this maven dependency:

<dependency>
    <groupId>com.bazaarvoice.auth</groupId>
    <artifactId>jersey-hmac-auth-client</artifactId>
    <version>${version}</version>
</dependency>

(2) Add the HmacClientFilter to your Jersey client:

Client client;              // this is your Jersey client constructed someplace else
client.addFilter(new HmacClientFilter(apiKey, secretKey, client.getMessageBodyWorkers()));

Python Client

To implement a Python client that constructs requests encoded for HMAC authentication, please refer to the python-hmac-auth library.