Getting started with Deadbolt
Before we get started, it's important to note that v2.4.4 has a slightly different API to v2.4.3 due to the addition of AuthenticatedRequests
. This breaking change was introduced to easier access to the subject, at the request of many people. If you're using an earlier version of the Deadbolt 2.4 series, please refer to the v2.4.3 documentation.
To use Deadbolt in your Play project, you will need to import the following library in build.sbt
.
libraryDependencies ++= Seq(
"be.objectify" %% "deadbolt-scala" % "2.6.0"
)
resolvers += Resolver.sonatypeRepo("snapshots")
If you're already running your app in activator, don't forgot to reload the project.
Once this is done, you can enable the Deadbolt module in conf/application.conf
.
play {
modules {
enabled += be.objectify.deadbolt.scala.DeadboltModule
}
}
This module provides two of the three components required for route filtering; DeadboltFilter
and FilterConstraints
. The third component will be a class you write, extending be.objectify.deadbolt.scala.filters.AuthorizedRoutes
. This extended class defines the route constraints, and needs to be bound into the dependency injection context.
Take a simple routes
file, which has two routes; one is a static route, the other is dynamic. A static route is simply one that does not contain variables.
GET /profile controllers.Application.profile
GET /view/:foo/:bar controllers.Application.view(foo: String, bar: String)
Authorization for these routes could be defined as follows - the static route (/profile
) only requires that a subject be present, while the dynamic route requires that a subject be present and have a role giving membership of a certain group.
import javax.inject.Inject
import be.objectify.deadbolt.scala.allOfGroup
import be.objectify.deadbolt.scala.filters.{AuthorizedRoute, AuthorizedRoutes, FilterConstraints}
import be.objectify.deadbolt.scala.filters._
class MyAuthorizedRoutes @Inject() (filterConstraints: FilterConstraints) extends AuthorizedRoutes {
override val routes: Seq[AuthorizedRoute] =
Seq(AuthorizedRoute(Get, "/profile", filterConstraints.subjectPresent, handler=None),
AuthorizedRoute(Any, "/view/$foo<[^/]+>/$bar<[^/]+>", filterConstraints.restrict(allOfGroup("someRole"))), handler=None)
}
One of the first things that becomes apparent on reading this code is /view/:foo/:bar
is suddenly /view/$foo<[^/]+>/$bar<[^/]+>
. This is because Play compiles the routes
file - however, don't worry! These compiled routes will be displayed if you visit any invalid URL in development mode (I tend to use something like http://localhost:9000/@foo) - this means you can copy and paste the exact route definition straight out of the browser. Note that /profile
remains unchanged - this is because it is a static route.
Taking a closer look at a single route authorization, you can see it has four parameters. There are
- the method. All common methods are defined in
be.objectify.deadbolt.scala.filters._
. The method is simply the HTTP method name as anOption
. There is alsoAny
, which means any method for that path pattern will cause a match. - the path pattern.
- the constraint. All Deadbolt constraints, including composite constraints, can be used. Use the
filterConstraints
instance to create these constraints. - the handler to use for this constraint. This has a default value of None, meaning the handler provided by
HandlerCache.apply()
will be used.
Because handler
is optional, a route can be expressed with three parameters.
AuthorizedRoute(Get, "/profile", filterConstraints.subjectPresent)
On the other hand, if you want to use different handlers for different routes, just use the HandlerCache
instance to provide them.
import javax.inject.Inject
import be.objectify.deadbolt.scala.cache.HandlerCache
import be.objectify.deadbolt.scala.allOfGroup
import be.objectify.deadbolt.scala.filters.{AuthorizedRoute, AuthorizedRoutes, FilterConstraints}
import be.objectify.deadbolt.scala.filters._
class MyAuthorizedRoutes @Inject() (filterConstraints: FilterConstraints, handlers: HandlerCache) extends AuthorizedRoutes {
override val routes: Seq[AuthorizedRoute] =
Seq(AuthorizedRoute(Get, "/profile", filterConstraints.subjectPresent, handlers("some handler")),
AuthorizedRoute(Any, "/view/$foo<[^/]+>/$bar<[^/]+>", filterConstraints.restrict(allOfGroup("someRole"))), handlers("some other handler"))
}
You now need to provide a binding for AuthorizedRoutes
. If you already have a custom module for your other Deadbolt integrations, you can put it in there, otherwise create a new module.
import be.objectify.deadbolt.scala.filters.AuthorizedRoutes
import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
class CustomDeadboltFilterHook extends Module {
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
bind[AuthorizedRoutes].to[MyAuthorizedRoutes]
)
}
Using compile-time dependency injection.
The examples above are for run-time dependency injection. If you prefer to use compile-time dependency injection instead, you can skip enabling DeadboltModule
and instead use DeadboltFilterComponents
to provide the necessary components; again, you will need to provide an instance of a class extending AuthorizedRoutes
yourself.
Updated over 5 years ago