Integrating Deadbolt
Implementations of the be.objectify.deadbolt.java.DeadboltHandler
interface are used to provide Deadbolt with what it needs to satisfy your project's authorization constraints. This interface has 4 primary functions.
getSubject
gets the current user from, for example, the cache or databasebeforeAuthCheck
run a pre-authorization task that can block further executiononAuthFailure
defines behaviour for when authorization requirements are not metgetDynamicResourceHandler
provides a hook into the dynamic constraint types
If you choose to associate permissions with roles, you should implement the getPermissionsForRole
method. This makes it easy to assign permissions when a subject is assigned a role, and can then be used by the RoleBasedPermissions constraint. RoleBasedPermissions
requires a subject to have at least one permission that matches at least one of the permissions obtained from getPermissionsForRole
. The default implementation of this method returns a completed future containing an empty list.
Authorization success
If you want to know when authorization has succeeded, implement the onAuthSuccess(Http.Context context, String constraintType, ConstraintPoint constraintPoint)
method. This method is invoked every time a constraint allows an operation to proceed.
context
can be used to determine request-specific informationconstraintType
is the name of the constraint, e.g.Dynamic
,SubjectPresent
, etcconstraintPoint
indicates where the constraint was applied. Constraints applied using annotations (ConstraintPoint.CONTROLLER
) and the routes (ConstraintPoint.FILTER
) only occur once per request, but it's possible for multiple authorizations to occur when using template constraints (ConstraintPoint.TEMPLATE
). UsingconstraintPoint
to differentiate can improve the performance of your implementation.
Dynamic constraint support
You only need to implement be.objectify.deadbolt.java.DynamicResourceHandler
if you're planning to use Dynamic
or Pattern.CUSTOM
constraints. Dynamic constraints are tests implemented entirely by your code. This interface has two functions:
isAllowed
is used by the Dynamic constraintcheckPermission
is used by the Pattern constraint when the pattern type isCUSTOM
Exposing the handler(s)
In order to expose your handler (or handlers - you can have more than one) to Deadbolt, you will need to implement the be.objectify.deadbolt.java.cache.HandlerCache
trait. This implementation needs to
- Provide a default handler; you can always use a specific handler in a template or controller, but if nothing is specified a well-known instance will be used.
- Provide a named instance
The simplest possible implementation of HandlerCache
only supports one DeadboltHandler
, in which case both get
and apply
always return the same instance.
@Singleton
public class MySimpleHandlerCache implements HandlerCache
{
private final DeadboltHandler defaultHandler = new MyDeadboltHandler();
@Override
public DeadboltHandler apply(final String key)
{
return defaultHandler;
}
@Override
public DeadboltHandler get()
{
return defaultHandler;
}
}
If you need to support multiple handlers, you will need some internal mechanism for differentiating between them based on the key
parameter of the apply
method..
@Singleton
public class MyHandlerCache implements HandlerCache
{
private final DeadboltHandler defaultHandler = new MyDeadboltHandler();
private final Map<String, DeadboltHandler> handlers = new HashMap<>();
public MyHandlerCache()
{
handlers.put(HandlerKeys.DEFAULT.key, defaultHandler);
handlers.put(HandlerKeys.ALT.key, new MyAlternativeDeadboltHandler());
}
@Override
public DeadboltHandler apply(final String key)
{
return handlers.get(key);
}
@Override
public DeadboltHandler get()
{
return defaultHandler;
}
}
HandlerKeys
, in the above example, is just a class declaring constants that standardize handler names; because you can define specific handlers on a per-constraint basis, it makes sense to define them in a well-known place.
Note the use of ConfigKeys.DEFAULT_HANDLER_KEY
- this is the default handler key specified by all annotations. In previous versions of Deadbolt, annotation-driven constraints would use HandlerCache#apply(DEFAULT_HANDLER_KEY)
to obtain the handler and so the default handler had to be associated with DEFAULT_HANDLER_KEY
. As of Deadbolt 2.5.3, this has been improved and any annotation-driven constraint using the default handler key will instead use HandlerCache#get
.
import be.objectify.deadbolt.java.ConfigKeys;
public class HandlerNames {
public static final String DEFAULT = ConfigKeys.DEFAULT_HANDLER_KEY;
public static final String ALT = "alt-handler";
private HandlerNames() {}
}
Finally, expose your handlers to Deadbolt. To do this, you will need to create a small module that binds your handler cache by type...
package com.example.modules
import be.objectify.deadbolt.java.cache.HandlerCache;
import play.api.Configuration;
import play.api.Environment;
import play.api.inject.Binding;
import play.api.inject.Module;
import scala.collection.Seq;
import security.MyHandlerCache;
import javax.inject.Singleton;
public class CustomDeadboltHook extends Module
{
@Override
public Seq<Binding<?>> bindings(final Environment environment,
final Configuration configuration)
{
return seq(bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
}
}
...and add it to your application.conf
.
play {
modules {
enabled += be.objectify.deadbolt.scala.DeadboltModule
enabled += com.example.modules.CustomDeadboltHook
}
}
Injecting handlers into the HandlerCache
In the example handler cache above, the DeadboltHandler instance is created directly within the object. What happens if you want to inject it? Just use the existing dependency injection mechanism.
public class CustomDeadboltHook extends Module
{
@Override
public Seq<Binding<?>> bindings(final Environment environment,
final Configuration configuration)
{
return seq(bind(DeadboltHandler.class).to(MyDeadboltHandler.class).in(Singleton.class),
bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
}
}
Your handler cache can now be injected with that instance.
@Singleton
public class MyHandlerCache implements HandlerCache
{
private final DeadboltHandler defaultHandler;
private final Map<String, DeadboltHandler> handlers = new HashMap<>();
@Inject
public MyHandlerCache(final DeadboltHandler handler)
{
this.defaultHandler = handler;
handlers.put(HandlerKeys.DEFAULT.key, defaultHandler);
handlers.put(HandlerKeys.ALT.key, new MyAlternativeDeadboltHandler());
}
// ...
}
But, what happens if you want to have multiple handlers defined and injected? The most flexible mechanism is to qualify the bindings through the use of custom annotations. For the following example, we will have two handler implementations; scaling up to more handlers just follows the same logical process.
This example uses Guice, which is the default DI framework of Play; if you're using a different DI framework, you will need to check how it supports multiple bindings of an interface.
First, create one annotation per handler implementation. These annotations will be annotated with com.google.inject.BindingAnnotation
.
import com.google.inject.BindingAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class HandlerQualifiers
{
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.PARAMETER})
@BindingAnnotation
public @interface MainHandler
{
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.PARAMETER})
@BindingAnnotation
public @interface SomeOtherHandler
{
}
}
You can now use these annotations on their respective handlers.
@HandlerQualifiers.MainHandler
public class MyDeadboltHandler extends AbstractDeadboltHandler
Create a constructor in your handler cache which accepts the handlers. The parameters use the same annotations to identify which instance will be passed as an argument for that parameter.
public class MyHandlerCache implements HandlerCache
{
private final DeadboltHandler handler;
private final Map<String, DeadboltHandler> handlers = new HashMap<>();
@Inject
public MyHandlerCache(@HandlerQualifiers.MainHandler final DeadboltHandler handler,
@HandlerQualifiers.SomeOtherHandler final DeadboltHandler otherHandler)
{
this.handler = handler;
this.handlers.put(handler.handlerName(),
handler);
this.handlers.put(otherHandler.handlerName(),
otherHandler);
}
@Override
public DeadboltHandler apply(final String name)
{
return handlers.get(name);
}
@Override
public DeadboltHandler get()
{
return handler;
}
}
The final step is to update your bindings to use the qualifiers.
public class CustomDeadboltHook extends Module
{
@Override
public Seq<Binding<?>> bindings(final Environment environment,
final Configuration configuration)
{
return seq(bind(DeadboltHandler.class).qualifiedWith(HandlerQualifiers.MainHandler.class).to(MyDeadboltHandler.class).in(Singleton.class),
bind(DeadboltHandler.class).qualifiedWith(HandlerQualifiers.SomeOtherHandler.class).to(SomeOtherDeadboltHandler.class).in(Singleton.class),
bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
}
}
Updated less than a minute ago