4. Using Deadbolt 2 with Play 2 Java projects

Deadbolt 2 for Java provides an idiomatic API for dealing with Java controllers and templates rendered from Java controllers in Play applications. It takes advantage of the features such as access to the HTTP context to give access to the current request and session, and the annotation-driven interceptor support.

4.1 The Deadbolt Handler

For any module - or framework - to be useable, it must provide a mechanism by which it can be hooked into your application. For Deadbolt, the central hook is the be.objectify.deadbolt.java.DeadboltHandler interface. The four methods defined by this interface are crucial to Deadbolt - for example, DeadboltHandler#getSubject gets the current subject (if any), whereas DeadboltHandler#onAccessFailure is used to generate a response when authorization fails. There is also a default method called handlerName which will be discussed in the section on handler caches.

DeadboltHandler implementations should be stateless.

For each method, a CompletionStage is returned - this is Java 8 interface for a future. If the future may complete to have an empty value, e.g. calling getSubject when no subject is present, the return type is CompletionStage<Optional>.

An application may have one or more handler implementations, which will be explored when we discuss the HandlerCache. Despite the use of the definite article in the section title, you can have as many Deadbolt handlers in your app as you wish. These can be specified on a per-constraint basis, allowing tight control over your authorization configuration if required.

AbstractDeadboltHandler

Deadbolt provides an abstract implementation of DeadboltHandler, called - in time-honoured fashion - AbstractDeadboltHandler. This provides the following behaviour:

  • No pre-authorization operations are carried out; a future containing an empty option is returned.
  • No subject is present; a future containing an empty option is returned.
  • No dynamic resource handler is present; as you can probably guess by now, a future containing an empty option is returned.
  • Authorization failure gives the standard Play unauthorized template rendered into a HTTP 401 response.

AbstractDeadboltHandler takes an instance of be.objectify.deadbolt.java.ExecutionContextProvider in its constructor. When using methods such as CompletableFuture#supplyAsync, this should be used to provide the executor necessary to ensure cross-thread access to the context.

Enable cross-thread access to the context
1 public CompletionStage<Result> onAuthFailure(final Http.Context context,
2                                              final Optional<String> content) {
3     final ExecutionContext executionContext = executionContextProvider.get();
4     final ExecutionContextExecutor executor = HttpExecution.fromThread(executionContext);
5     return CompletableFuture.supplyAsync(unauthorized::render,
6                                          executor)
7                             .thenApplyAsync(Results::unauthorized,
8                                             executor);
9 }

Performing pre-constraint operations

Before a constraint is applied, the CompletionStage<Optional<Result>> beforeAuthCheck(Http.Context context) method of the current handler is invoked. If the resulting promise completes to a non-empty Optional, the target action is not invoked and instead the result of beforeAuthCheck is used for the HTTP response; if the resulting promise completes to an empty Optional the action is invoked with the Deadbolt constraint applied to it.

You may want to use this method to test if a subject is present. However, it’s important to be aware that this approach will actually shortcut the use of some annotations and result in inconsistent behaviour. The simplest example is the “subject not present” constraint, which requires no subject to be present for the action to be authorized. If you pre-emptively check for a subject in beforeAuthCheck and fail if one isn’t present, this means the “subject not present” check will fail! It’s recommended to use the constraints to drive this behaviour, and handle the consequences of failure in the onAccessFailure method.

If you don’t want to carry out any action before authorization constraints are applied, just return a completed future containing an empty option. Alternatively, extend AbstractDeadboltHandler and do not override this method.

Obtaining the subject

To get the current subject, the CompletionStage<Optional<Subject>> getSubject(Http.Context context) method is invoked. Returning an empty Optional indicates there is no subject present - this is a completely valid scenario, typically indicating no successful authentication has yet taken place.

By default, subjects are not cached and so this method is called every time the subject is required. If you have multiple constraints, for example if you’re using a lot of template constraints, this can become quite expensive. If you want the subject to be cached on a per-request basis, add deadbolt.java.cache-user=true to your configuration. Once this is done, caching will be handled automatically and this method will then only be called when no subject is already cached.

How the subject is obtained depends largely on how you authenticate subjects. You may simply get some identifier from a cookie and use it to look up a subject in the database; alternatively, your authentication library may provide support for access to subjects.

Dealing with authorization failure

When authorization fails, the CompletionStage<Result> onAccessFailure(Http.Context context, String content) method is used to obtain a result for the HTTP response. The result required from the CompletionStage returned from this method is a regular play.mvc.Result, so it can be anything you chose. You might want to return a 403 forbidden, redirect to a location accessible to everyone, etc.

Dealing with dynamic constraints

Dynamic constraints, which are Dynamic and Pattern.CUSTOM constraints, are dealt with by implementations of DynamicResourceHandler; this will be explored in a later chapter. For now, it’s enough to say CompletionStage<Optional<DynamicResourceHandler>> getDynamicResourceHandler(Http.Context context) is invoked when a dynamic constraint is used.

If you don’t have any dynamic constraints, just return a completed future containing an empty option. Alternatively, extend AbstractDeadboltHandler and do not override this method.

4.2 Expose your DeadboltHandlers with a HandlerCache

Unlike earlier versions of Deadbolt, in which handlers were declared in application.conf and created reflectively, Deadbolt now uses dependency injection to achieve the same functionality in a type-safe and more flexible manner. Various components of Deadbolt, which will be explored in later chapters, require an instance of be.objectify.deadbolt.java.cache.HandlerCache - however, no such implementations are provided.

Instead, you need to implement your own version. This has two requirements:

  • You have a get() method which returns the application-wide default handler
  • You have an apply(String handlerKey) method which returns a named handler

NB One interesting (and annoying) quirk is the way in which template and controller constraints obtain the default handler. The template constraints are written in Scala, so the HandlerCache#get() method can be used. Controllers, on the other hand, are configured via annotations and it’s not possible to have a default value of null for an annotation value and so the standard handler name defined by be.objectify.deadbolt.java.ConfigKeys.DEFAULT_HANDLER_KEY; is used. In the example below, HandlerKeys is an class that defines the handler names used in the application. To make sure we use the necessary default handler key, we re-use the ConfigKeys constant here.

Centralising the names of handlers
 1 public enum HandlerKeys
 2 {
 3     DEFAULT(ConfigKeys.DEFAULT_HANDLER_KEY),
 4     ALT("altHandler");
 5 
 6     public final String key;
 7 
 8     private HandlerKeys(final String key)
 9     {
10         this.key = key;
11     }
12 }

Here’s one possible implementation, using hard-coded handlers. defaultHandler is kept separate to make the get method more efficient, instead up looking it up in the handlers map every time.

Defining hard-coded handlers
 1 @Singleton
 2 public class MyHandlerCache implements HandlerCache {
 3 
 4     private final Map<String, DeadboltHandler> handlers = new HashMap<>();
 5 
 6     private final DeadboltHandler defaultHandler = new MyDeadboltHandler();
 7 
 8     // handler keys is an application-specific enum
 9     public MyHandlerCache() {
10         // See below regarding the default handler
11         handlers.put(HandlerKeys.DEFAULT.key, defaultHandler);
12         handlers.put(HandlerKeys.ALT.key, new MyAlternativeDeadboltHandler());
13     }
14 
15     @Override
16     public DeadboltHandler apply(final String key) {
17         return handlers.get(key);
18     }
19 
20     @Override
21     public DeadboltHandler get() {
22         return defaultHandler;
23     }
24 }

Ideally, handler implementations should be self-sufficient. To this end, the DeadboltHandler interface contains a default method called handlerName. By default, this returns the class name. To make it more useful, implement the method and specific the handler name in it.

Overriding the handler name
1 public class MyDeadboltHandler implements DeadboltHandler {
2 
3     // ...
4 
5     public String handlerName() {
6         return HandlerKeys.DEFAULT.key;
7     }
8 }

This means we can change the previous implementation to the following.

Using the handlerName method to map handlers
 1 @Singleton
 2 public class MyHandlerCache implements HandlerCache {
 3 
 4     private final Map<String, DeadboltHandler> handlers = new HashMap<>();
 5 
 6     private final DeadboltHandler defaultHandler = new MyDeadboltHandler();
 7 
 8     // handler keys is an application-specific enum
 9     public MyHandlerCache() {
10         handlers.put(defaultHandler.handlerName(), defaultHandler);
11         final DeadboltHandler altHandler = new MyAlternativeDeadboltHandler();
12         handlers.put(altHandler.handlerName(), altHandler);
13     }
14 
15     // ...
16 }

Finally, create a small module which binds your implementation.

Binding your HandlerCache implementation
 1 package com.example.modules
 2 
 3 import be.objectify.deadbolt.java.cache.HandlerCache;
 4 import play.api.Configuration;
 5 import play.api.Environment;
 6 import play.api.inject.Binding;
 7 import play.api.inject.Module;
 8 import scala.collection.Seq;
 9 import security.MyHandlerCache;
10 
11 import javax.inject.Singleton;
12 
13 public class CustomDeadboltHook extends Module {
14     @Override
15     public Seq<Binding<?>> bindings(final Environment environment,
16                                     final Configuration configuration) {
17         return seq(bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
18     }
19 }

Using dependency injection with handlers

Both of these examples would work fine, but ignore the wide-spread and recommended use of dependency injection in Play 2.5. You may find it more useful to inject handlers into the handler cache, especially if those handlers themselves rely on injected components. As we’ve already seen, it’s possible to have multiple handler implementations so we also need a way to distinguish between implementations during injection.

The trick here is to qualify the handlers. This example will use the Guice approach; if you’re using a different DI framework, you need to check if it has support for a similar mechanism. If your application has a single handler implementation, you can skip this step and just declare the binding and inject the instance in the normal way.

Binding annotations for multiple handlers
 1 import com.google.inject.BindingAnnotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 public class HandlerQualifiers
 9 {
10     @Retention(RetentionPolicy.RUNTIME)
11     @Target({ElementType.TYPE, ElementType.PARAMETER})
12     @BindingAnnotation
13     public @interface MainHandler {}
14 
15     @Retention(RetentionPolicy.RUNTIME)
16     @Target({ElementType.TYPE, ElementType.PARAMETER})
17     @BindingAnnotation
18     public @interface AltHandler {}
19 }

These annotations can then be used to tag specific implementations in a way that can be accessed during the injection phase. For example, MyDeadboltHandler now has the following declaration; MyAlternativeDeadboltHandler has a similar declaration with the appropriate annotation.

Use annotations to label implementations
1 @HandlerQualifiers.MainHandler
2 public class MyDeadboltHandler implements DeadboltHandler

The bindings method of the module needs to be updated to be aware of the handler implementations.

Use annotations to label implementations
 1 public Seq<Binding<?>> bindings(final Environment environment,
 2                                     final Configuration configuration) {
 3     return seq(bind(DeadboltHandler.class)
 4                         .qualifiedWith(HandlerQualifiers.MainHandler.class)
 5                         .to(MyDeadboltHandler.class)
 6                         .in(Singleton.class),
 7                bind(DeadboltHandler.class)
 8                         .qualifiedWith(HandlerQualifiers.AltHandler.class)
 9                         .to(MyAlternativeDeadboltHandler.class)
10                         .in(Singleton.class),
11                bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
12 }

Now, it’s just a question of injecting the qualified handlers into the HandlerCache.

Injecting handlers into the cache
 1 @Singleton
 2 public class MyHandlerCache implements HandlerCache {
 3 
 4     private final Map<String, DeadboltHandler> handlers = new HashMap<>();
 5 
 6     private final DeadboltHandler defaultHandler;
 7 
 8     @Inject
 9     public MyHandlerCache(@HandlerQualifiers.MainHandler
10                                 final DeadboltHandler defaultHandler,
11                           @HandlerQualifiers.AltHandler final DeadboltHandler altHandler) {
12         this.defaultHandler = defaultHandler;
13 
14         handlers.put(defaultHandler.handlerName(), defaultHandler);
15         handlers.put(altHandler.handlerName(), altHandler);
16     }
17 
18     // ...
19 }

4.3 application.conf

Declare the necessary modules

Both be.objectify.deadbolt.java.DeadboltModule and your custom bindings module must be declared in the configuration.

Binding your HandlerCache implementation
1 play {
2   modules {
3     enabled += be.objectify.deadbolt.java.DeadboltModule
4     enabled += com.example.modules.CustomDeadboltHook
5   }
6 }

Tweaking Deadbolt

Deadbolt Java-specific configuration lives in the deadbolt.java namespace.

  • deadbolt.java.view-timeout
    • The millisecond timeout applied to blocking calls when rendering templates. Defaults to 1000ms.
  • deadbolt.java.cache-user
    • A flag to indicate if the subject should be cached on a per-request basis. This defaults to false, but setting it to true may result in significant performance gains if you’re using template constraints.

Personally, I prefer the HOCON (Human-Optimized Config Object Notation) syntax supported by Play, so I would recommend the following:

Tweaking the default settings
1 deadbolt {
2   java {
3     cache-user=true
4     view-timeout=500
5   }
6 }

JPA

After all the effort I made to ensure Deadbolt is as non-blocking as possible, JPA emerged from the mist to bite me on the ass; entity managers, it seems, do not like the kind of multi-threaded usage implicit in Play’s asynchronous behaviour.

Luckily, a solution is at hand. Less luckily, it’s blocking.

To address this, you can put Deadbolt into blocking mode - this ensures all DB calls made in the Deadbolt layer are made from the same thread; this has performance implications, but it’s unavoidable with JPA.

To switch to blocking mode, set deadbolt.java.blocking to true in your configuration.

The default timeout is 1000 milliseconds - to change this, use deadbolt.java.blocking-timeout in your configuration.

This example configuration puts Deadbolt in blocking mode, with a timeout of 2500 milliseconds:

Surrendering to the whims of JPA
1 deadbolt {
2   java {
3     blocking=true
4     blocking-timeout=2500
5   }
6 }