-
Notifications
You must be signed in to change notification settings - Fork 31
How to Use
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.
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>
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.
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.
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.
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.
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.
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.
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.
Todo...
- 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 RequestHandler
s 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
The following clients are available, but note that you can write your own in any language as long as it follows the appropriate contract.
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()));
To implement a Python client that constructs requests encoded for HMAC authentication, please refer to the python-hmac-auth library.