-
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 help callers interface smoothly with your 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.
The PassThroughRequestHandler
class is a "fake" request handler that may be useful for testing. It bypasses authentication completely and just passes a dummy principal to the Jersey resource method that is called to handle the request.
The final step to implement jersey-hmac-auth on your server is to register it with Jersey. For example, using Dropwizard:
environment.addProvider(new HmacAuthProvider(new DefaultRequestHandler(new MyAuthenticator())));
When calling an API that uses jersey-hmac-auth, callers must build requests such that they can be authenticated by the server. For instance, they must pass their API key as a query parameter and generate a signature and pass it along with a other related parameters as headers on the request.
To make this really easy for a caller, you can create a client library (SDK) that encapsulates this behavior. For instance, your client library could provide methods that take the caller's API key and secret key and then, under the covers, generates a signature and adds the appropriate parameters/headers to the request.
There are a couple client-side libraries available to that you can incorporate in your SDK so that it easily interfaces with an API that uses jersey-hmac-auth on the server. The following provides more details about these. (In addition to these, note that you can also implement similar support in any language just as long as it implements the protocol.)
You can use the following to implement client-side support in your Java client (using Jersey). This will add a filter to your Jersey client that will modify outgoing HTTP requests so that they can be authenticated on the server. The filter modifies requests by generating a signature and adding all appropriate parameters/headers.
- Add this maven dependency:
<dependency>
<groupId>com.bazaarvoice.auth</groupId>
<artifactId>jersey-hmac-auth-client</artifactId>
<version>${version}</version>
</dependency>
- Add the
HmacClientFilter
to your Jersey client (assuming you have already have a Jersey client instance):
client.addFilter(new HmacClientFilter(yourApiKey, yourSecretKey, client.getMessageBodyWorkers()));
To add client-side support in a Python client, please refer to the python-hmac-auth library.
After you secure your API, you may find it difficult to interface with it via the command line using tools like curl
. The jersey-hmac-auth library provides an HMAC-aware utility called hurl
. The hurl
utility is similar to curl
, but allows you to also specify an API key and secret key, which it uses to add the appropriate parameters/headers to the request.
To see more details about hurl
, please refer to the cli tool in this repository.