Deadbolt 2 — это библиотека авторизации для Play 2, в которой есть API для приложений на базе Java и Scala. Это позволяет применять ограничения к действиям контроллера и настраивать рендеринг шаблонов на основе текущего пользователя.
Вот краткое, краткое руководство по началу работы с версией Scala.
зависимости
Добавьте зависимость в вашу сборку
"be.objectify" %% "deadbolt-scala" % "2.4.2"
Добавьте модуль Deadbolt в свое приложение Play
play {
modules {
enabled += be.objectify.deadbolt.scala.DeadboltModule
}
}
Если вы уже запускаете свое приложение в активаторе, не забудьте перезагрузить проект.
Модель предметной области
Реализуйте Subject
, Role
и Permission
черты.
Subject
представляет, как правило, пользователя- A
Role
— это одна системная привилегия, например ** admin **, ** user ** и т. Д. Субъект может иметь ноль или более ролей. - A
Permission
может использоваться с сопоставлением регулярного выражения, например, субъект с разрешением `printers.admin` может получить доступ к ресурсу, ограниченному для` printers. * `,` * .Admin` и т. Д. У субъекта может быть ноль или более разрешения.
Крючки
Реализуйте be.objectify.deadbolt.scala.DeadboltHandler
черту. Эта реализация (или реализации — вы можете более одного) используется для
- получить текущего пользователя —
getSubject
- запустить задачу предварительной авторизации, которая может заблокировать дальнейшее выполнение —
beforeAuthCheck
- обработать ошибку авторизации —
onAuthFailure
- обеспечить связь с динамическими типами ограничений —
getDynamicResourceHandler
Вам нужно только реализовать, be.objectify.deadbolt.scala.DynamicResourceHandler
если вы планируете использовать Dynamic
или Pattern.CUSTOM
ограничения. Динамические ограничения — это тесты, реализованные полностью вашим кодом. Эта черта имеет две функции:
isAllowed
используетсяDynamic
ограничениемcheckPermission
используетсяPattern
ограничением, когда тип шаблонаCUSTOM
Реализуйте be.objectify.deadbolt.scala.HandlerCache
черту. Это используется Deadbolt для получения экземпляров DeadboltHandler
s и имеет две концепции
- Обработчик по умолчанию. Вы всегда можете использовать определенный обработчик в шаблоне или контроллере, но если ничего не указано, будет использован известный экземпляр.
- Именованные обработчики.
Ниже приведен пример реализации на основе примера приложения.
@Singleton
class MyHandlerCache extends HandlerCache {
val defaultHandler: DeadboltHandler = new MyDeadboltHandler
// HandlerKeys is an user-defined object, containing instances of a case class that extends HandlerKey
val handlers: Map[Any, DeadboltHandler] = Map(HandlerKeys.defaultHandler -> defaultHandler,
HandlerKeys.altHandler -> new MyDeadboltHandler(Some(MyAlternativeDynamicResourceHandler)),
HandlerKeys.userlessHandler -> new MyUserlessDeadboltHandler)
// Get the default handler.
override def apply(): DeadboltHandler = defaultHandler
// Get a named handler
override def apply(handlerKey: HandlerKey): DeadboltHandler = handlers(handlerKey)
}
Наконец, выставьте своих обработчиков Deadbolt. Для этого вам нужно создать небольшой модуль, который связывает ваш кеш обработчика по типу…
package com.example.modules
import be.objectify.deadbolt.scala.cache.HandlerCache
import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
import com.example.security.MyHandlerCache
class CustomDeadboltHook extends Module {
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
bind[HandlerCache].to[MyHandlerCache]
)
}
… и добавьте его в ваше application.conf
play {
modules {
enabled += be.objectify.deadbolt.scala.DeadboltModule
enabled += com.example.modules.CustomDeadboltHook
}
}
Использование инъекции зависимостей во время компиляции
Если вы предпочитаете связывать все вместе с зависимостями времени компиляции, вам не нужно создавать пользовательский модуль или добавлять DeadboltModule
в него play.modules
.
Вместо этого зависимости обрабатываются с использованием пользовательских ApplicationLoader
. Чтобы упростить задачу, через эту be.objectify.deadbolt.scala.DeadboltComponents
черту стали доступны различные компоненты Deadbolt . Вам все еще нужно будет предоставить несколько вещей, например, вашу HandlerCache
реализацию, и тогда вы получите доступ ко всем обычным элементам Deadbolt.
Вот пример ApplicationLoader для DI во время компиляции.
class CompileTimeDiApplicationLoader extends ApplicationLoader {
override def load(context: Context): Application
= new ApplicationComponents(context).application
}
class ApplicationComponents(context: Context)
extends BuiltInComponentsFromContext(context)
with DeadboltComponents
with EhCacheComponents {
// Define a pattern cache implementation
// defaultCacheApi is a component from EhCacheComponents
override lazy val patternCache: PatternCache = new DefaultPatternCache(defaultCacheApi)
// Declare something required by MyHandlerCache
lazy val subjectDao: SubjectDao = new TestSubjectDao
// Specify the DeadboltHandler implementation to use
override lazy val handlers: HandlerCache = new MyHandlerCache(subjectDao)
// everything from here down is application-level
// configuration, unrelated to Deadbolt, such as controllers, routers, etc
// ...
}
Компоненты, предоставляемые Deadbolt:
scalaAnalyzer
— логика ограниченияdeadboltActions
— для составления действийactionBuilders
— для строительных мероприятийviewSupport
— для шаблонных ограниченийpatternCache
— для кеширования регулярных выражений. Вы должны определить это самостоятельно в загрузчике приложения, но, как в примере выше, легко использовать реализацию по умолчаниюhandlers
— реализация того,HandlerCache
что вы предоставляетеconfiguration
— конфигурация приложенияecContextProvider
— контекст выполнения для параллельных операций. По умолчаниюscala.concurrent.ExecutionContext.global
templateFailureListenerProvider
— для прослушивания ошибок, связанных с Deadbolt, которые возникают при рендеринге шаблонов. По умолчанию для реализации без операций
После того как вы определили свой ApplicationLoader
, вы должны добавить его в свой application.conf
.
play {
application {
loader=com.example.myapp.CompileTimeDiApplicationLoader
}
}
Какой бы подход вы ни внедрили, теперь вы готовы обеспечить безопасный доступ к функциям контроллера и шаблонам в приложении Play 2.
Ограничения контроллера с помощью Action Builder
Используя ActionsBuilders
класс, вы можете быстро собрать ограничения вокруг ваших функций. Чтобы начать, введите ActionBuilders
в свой контроллер.
class ExampleController @Inject() (actionBuilder: ActionBuilders) extends Controller
Теперь у вас есть конструкторы для всех типов ограничений, которые мы кратко рассмотрим через минуту. В следующих примерах я использую обработчик по умолчанию, т. .defaultHandler()
Е. Также можно использовать другой обработчик с помощью .key(HandlerKey)
или передать непосредственно обработчик .withHandler(DeadboltHandler)
.
SubjectPresent и SubjectNotPresent
Иногда вам не нужны детальные проверки — вам просто нужно посмотреть, присутствует ли пользователь (или нет).
// DeadboltHandler#getSubject must result in a Some for access to be granted
def someFunctionA = actionBuilder.SubjectPresentAction().defaultHandler() { Ok(accessOk()) }
// DeadboltHandler#getSubject must result in a None for access to be granted
def someFunctionB = actionBuilder.SubjectNotPresentAction().defaultHandler() { Ok(accessOk()) }
ограничивать
Это использует Subject
s Role
для выполнения проверок И / ИЛИ / НЕ. Значения, данные строителю, должны соответствовать Role.name
ролям субъекта.
И определяется как Array[String]
(или, точнее String*
, ИЛИ — это List[Array[String]]
, а НЕ — это имя-ролик с !
предшествующим ему).
// subject must have the "foo" role
def restrictedFunctionA = actionBuilder.RestrictAction("foo").defaultHandler() { Ok(accessOk()) }
// subject must have the "foo" AND "bar" roles
def restrictedFunctionB = actionBuilder.RestrictAction("foo", "bar").defaultHandler() { Ok(accessOk()) }
// subject must have the "foo" OR "bar" roles
def restrictedFunctionC = actionBuilder.RestrictAction(List(Array("foo"), Array("bar"))).defaultHandler() { Ok(accessOk()) }
Шаблон
При этом используется Subject
«s Permission
s для выполнения различных проверок.
// subject must have a permission with the exact value "admin.printer"
def permittedFunctionA = actionBuilders.PatternAction("admin.printer", PatternType.EQUALITY).defaultHandler() { Ok(accessOk()) }
// subject must have a permission that matches the regular expression (without quotes) "(.)*\.printer"
def permittedFunctionB = actionBuilders.PatternAction("(.)*\.printer", PatternType.REGEX).defaultHandler() { Ok(accessOk()) }
// the checkPermssion function of the current handler's DynamicResourceHandler will be used. This is a user-defined test
def permittedFunctionC = actionBuilders.PatternAction("something arbitrary", PatternType.CUSTOM).defaultHandler() { Ok(accessOk()) }
динамический
Наиболее гибкое ограничение — это полностью определяемое пользователем ограничение, которое используется DynamicResourceHandler#isAllowed
для определения доступа.
def foo = actionBuilder.DynamicAction(name = "someClassifier").defaultHandler() { Ok(accessOk()) }
Ограничения контроллера с составом действия
Используя DeadboltActions
класс, вы можете создавать ограниченные функции. Чтобы начать, введите DeadboltActions
в свой контроллер.
class ExampleController @Inject() (deadbolt: DeadboltActions) extends Controller
Теперь у вас есть функции, эквивалентные функциям сборщиков, упомянутых выше. В следующих примерах я использую обработчик по умолчанию, т.е. обработчик не указан, но также можно использовать другой обработчик handler = <some handler, possibly from the handler cache>
.
SubjectPresent и SubjectNotPresent
// DeadboltHandler#getSubject must result in a Some for access to be granted
def someFunctionA = deadbolt.SubjectPresent() {
Action {
Ok(accessOk())
}
}
// DeadboltHandler#getSubject must result in a None for access to be granted
def someFunctionB = deadbolt.SubjectNotPresent() {
Action {
Ok(accessOk())
}
}
ограничивать
// subject must have the "foo" role
def restrictedFunctionA = deadbolt.Restrict(List(Array("foo")) {
Action {
Ok(accessOk())
}
}
// subject must have the "foo" AND "bar" roles
def restrictedFunctionB = deadbolt.Restrict(List(Array("foo", "bar")) {
Action {
Ok(accessOk())
}
}
// subject must have the "foo" OR "bar" roles
def restrictedFunctionC = deadbolt.Restrict(List(Array("foo"), Array("bar"))) {
Action {
Ok(accessOk())
}
}
Шаблон
// subject must have a permission with the exact value "admin.printer"
def permittedFunctionA = deadbolt.Pattern("admin.printer", PatternType.EQUALITY) {
Action {
Ok(accessOk())
}
}
// subject must have a permission that matches the regular expression (without quotes) "(.)*\.printer"
def permittedFunctionB = deadbolt.Pattern("(.)*\.printer", PatternType.REGEX) {
Action {
Ok(accessOk())
}
}
// the checkPermssion function of the current handler's DynamicResourceHandler will be used. This is a user-defined test in DynamicResourceHandler#checkPermission
def permittedFunctionC = deadbolt.Pattern("something arbitrary", PatternType.CUSTOM) {
Action {
Ok(accessOk())
}
}
динамический
Наиболее гибкое ограничение — это полностью определяемое пользователем ограничение, которое используется DynamicResourceHandler#isAllowed
для определения доступа.
def foo = deadbolt.Dynamic(name = "someClassifier") {
Action {
Ok(accessOk())
}
}
Ограничения шаблона
Используя ограничения шаблонов, вы можете исключить создание частей шаблонов на стороне сервера. Это не манипулирование DOM на стороне клиента! Шаблонные ограничения имеют те же возможности, что и ограничения контроллера.
По умолчанию в шаблонных ограничениях используется стандартный обработчик Deadbolt, но, как и в случае с ограничениями контроллера, вы можете передать определенный обработчик. Самый простой способ сделать это — передать обработчик в шаблон, а затем передать его в ограничения. Еще одним преимуществом этого подхода является то, что вы можете передать упакованную версию обработчика, который будет кэшировать тему; если у вас много ограничений в шаблоне, это может дать значительный выигрыш.
Здесь важно отметить, что шаблоны блокируются, поэтому все используемые фьючерсы должны быть завершены, чтобы их можно было использовать в ограничениях на шаблоны. В результате каждое ограничение может принимать функцию, которая выражает Long, который является миллисекундным значением времени ожидания. По умолчанию это 1000 миллисекунд, но вы можете изменить это глобально, установив deadbolt.scala.view-timeout
значение в вашем application.conf
.
Каждое ограничение имеет вариант, который позволяет вам определять запасной контент. Это приходит в формате `<constraintName> Или`, например
@subjectPresentOr {
this is protected
} {
this will be shown if the constraint blocks the other content
}
SubjectPresent
@import be.objectify.deadbolt.scala.views.html.{subjectPresent, subjectPresentOr}
@subjectPresent() {
This content will be present if handler#getSubject results in a Some
}
@subjectPresentOr() {
This content will be present if handler#getSubject results in a Some
} {
fallback content
}
SubjectNotPresent
@import be.objectify.deadbolt.scala.views.html.{subjectNotPresent, subjectNotPresentOr}
@subjectNotPresent() {
This content will be present if handler#getSubject results in a None
}
@subjectNotPresentOr() {
This content will be present if handler#getSubject results in a None
} {
fallback content
}
ограничивать
@import be.objectify.deadbolt.scala.views.html.{restrict, restrictOr}
@restrict(roles = List(Array("foo", "bar"))) {
Subject requires the foo role for this to be visible
}
@restrict(List(Array("foo", "bar")) {
Subject requires the foo AND bar roles for this to be visible
}
@restrict(List(Array("foo"), Array("bar"))) {
Subject requires the foo OR bar role for this to be visible
}
@restrictOr(List(Array("foo", "bar")) {
Subject requires the foo AND bar roles for this to be visible
} {
Subject does not have the necessary roles
}
Шаблон
Тип шаблона по умолчанию PatternType.EQUALITY
.
@import be.objectify.deadbolt.scala.views.html.{pattern, patternOr}
@pattern("admin.printer") {
Subject must have a permission with the exact value "admin.printer" for this to be visible
}
@pattern("(.)*\.printer", PatternType.REGEX) {
Subject must have a permission that matches the regular expression (without quotes) "(.)*\.printer" for this to be visible
}
@pattern("something arbitrary", PatternType.CUSTOM) {
DynamicResourceHandler#checkPermission must result in true for this to be visible
}
@patternOr("admin.printer") {
Subject must have a permission with the exact value "admin.printer" for this to be visible
} {
Subject did not have necessary permissions
}
динамический
@import be.objectify.deadbolt.scala.views.html.{dynamic, dynamicOr}
@dynamic("someName") {
DynamicResourceHandler#isAllowed must result in true for this to be visible
}
@dynamicOr("someName") {
DynamicResourceHandler#isAllowed must result in true for this to be visible
} {
Custom test failed
}
Контекст выполнения
По умолчанию все фьючерсы выполняются в контексте `scala.concurrent.ExecutionContext.global`. Если вы хотите предоставить отдельный контекст выполнения, вы можете подключить его к Deadbolt, реализовав черту `DeadboltExecutionContextProvider`.
import be.objectify.deadbolt.scala.DeadboltExecutionContextProvider
class CustomDeadboltExecutionContextProvider extends DeadboltExecutionContextProvider {
override def get(): ExecutionContext = ???
}
NB. Этот провайдер вызывается дважды, один раз DeadboltActions
и один раз ViewSupport
.
После того, как вы внедрили провайдера, вам нужно объявить его в своем пользовательском модуле (см. Введение выше).
class CustomDeadboltHook extends Module {
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
bind[HandlerCache].to[MyHandlerCache],
bind[DeadboltExecutionContextProvider].to[CustomDeadboltExecutionContextProvider]
)
}
Что дальше?
Если вы хотите изучить Deadbolt дальше, вы можете взглянуть на книгу, которую я сейчас о ней пишу. Вы можете найти его на https://leanpub.com/deadbolt-2 .