play 1.x で利用できる認可メカニズムを実装できるモジュール"Deadbolt"のドキュメントを翻訳してみました( ).
誤訳があったら教えてもらえると助かります.
Deadbolt
Deadboltは単純なAND/OR/NOT構文を用いたコントローラメソッドやビューの一部のアクセス権限を定義する認可メカニズムです.
このモジュールはPlay! frameworkのオリジナルのSecureモジュールをベースにしています.
ただし,Deadboltモジュールは認証機構を提供しません.
認可機構を実装するために,DeadboltとPlay! frameworkのSecureモジュールを組み合わせて実装することができます.
また,認証システムがアプリ外で動く場合でも認可メカニズムを実装することができます.
インストール手順
Deadboltをインストールするためにはモジュールリポジトリを利用できる.
play install deadbolt-<required version>
Example
下記で紹介する全てのコード例はsamples-and-testsに格納されている.
ユーザの識別
アクセスしたコントローラやビューの認可があるか判断するためには,アクセスしたユーザが誰か知る必要があります.
アプリケーションによってユーザの定義や実装方法が異なるため,Deadboltではmodel.deadbolt.RoleHolderインターフェースを定義しています.
このインタフェースはコントローラやビューにアクセスしようとするオブジェクトの種類をすべて実装する必要があります.
package models;
import models.deadbolt.Role;
import models.deadbolt.RoleHolder;
import java.util.Arrays;
import java.util.List;
public class MyRoleHolder implements RoleHolder
{
public List<? extends Role> getRoles()
{
return Arrays.asList(new MyRole("foo"),
new MyRole("bar"),
new MyRole("restricted"));
}
}
一度RoleHolderを実装したら,RoleHolderがどんな役割(ロール)を持っているかを示すmodels.deadbolt.Roleのリストを返します.
さらに,Roleはインターフェースです.それゆえ.playのモデルアーキテクチャに割り込ませるべきではありません.
package models;
import models.deadbolt.Role;
public class MyRole implements Role
{
private String name;
public MyRole(String name)
{
this.name = name;
}
public String getRoleName()
{
return name;
}
}
Hooking Deadbolt into your application
ランタイムセキュリティに必要な情報を取得するために,controllers.deadbolt.DeadboltHandler インタフェースを実装する必要があります.
また,abstractバージョンとして controllers.deadbolt.AbstractDeadboltHandler があり、
こちらは,Deadbolt以外の制約事項を利用しないならば,便利に利用することができます.
DeadboltHanderインタフェースには4つのメソッドが定義されています.
- void beforeRoleCheck()
このメソッドは現在のRoleHolderの認可をチェックする前に実行されます.
これは認証機構に接続する基盤となります.つまり,このメソッドによって,ユーザが実際にログインされるかどうかチェックし,処理の分岐を行うことができます.
- RoleHolder getRoleHolder()
このメソッドでは現在のRoleHolderを返します.
- void onAccessFailure(String controllerClassName)
このメソッドは制限されたリソースに権限無いユーザがアクセスした場合に実行されます.
このメソッドでは,例えば,実行されたコントローラのクラスのログを出したり、適切な場所にユーザを遷移させるために利用します.
- ExternalizedRestrictionsAccessor getExternalizedRestrictionsAccessor()
もし、アプリケーションに外部の制約があるとき(例えば、DBやXMLファイルに設定されている場合)
ExternalizedRestrictionsAccessorを実装することによって,外部の制約へのアクセス方法をDeadboltに渡す必要があります.
このメソッドから外部制約を返すことによってDeadboltに渡すことができます.
アプリケーションで外部制約がない場合は,単純にnullを返せばよいです.
- RestrictedResourcesHandler getRestrictedResourcesHandler()
アプリケーションが動的な制約をサポートするならば,RestrictedResourcesHandlerを実装することによってそれらを評価する方法を提供する必要があります.
RestrictedResourcesHandlerの実装をこのメソッドから返すことによって,動的な制約の評価方法をDeadboltに渡すことができます.
アプリケーションで動的な制約を利用しないならば,単純にnullを返せばよいです.
一旦、DeadboltHandlerを実装したら、application.confファイルにそれを宣言できます.
DeadboltHandlerの実装には引数無しのコンストラクタが必要であることを忘れないでください.絶対にだ!
deadbolt.handler=controllers.MyDeadboltHandler
もし、フレームワークIDごとに異なるDeadboltHandlersを利用したい場合,Playのメカニズムを利用します.
%production.deadbolt.handler=controllers.ProductionDeadboltHandler
%test.deadbolt.handler=controllers.TestDeadboltHandler
コントローラへのアクセス制限の実装
コントローラのセキュリティを確保するために、@Withアノテーションを利用してDeadboltクラスと関連付けます。
Deadboltクラス内でカレントユーザの権限等をチェックします。
@With(Deadbolt.class)
public class MyController extends Controller
Dynamic restrictions(動的な制約)
動的な制約は名前付けされたリソースによって動作します。
例えば、クラスやメソッドやviewなど、あなたが実装したハンドラーに対して名前付けされたリソースを渡します。
このリソースの名前は,例えば,カレントユーザが持つロールやカレントユーザが所属するグループのロール,
時間的なものなど,任意の名前を付けることができます.
動的な制約は静的な制限よりも優先されます.
RestrictedResourcesHandler
RestrictResourceHandlerはリソースへのアクセスを定義するのに利用します.
前述したように,DeadboltHandlerはgetRestrictedResourcesHandler()メソッドを持ち,
ひとつ以上のRestrictResourceHandlerを返します.
これらのハンドラーは任意の理由に基づいて名前つきのリソースを渡します.
それ以降でアクセスを許可するかはあなたしだいです.
アクセスリクエストは3つの認可結果(ALLOWED, DENIED, NOT_SPECIFIED)のうちひとつをとりえます.
ALLOWEDは許可、DENIEDは禁止を示します.
NOT_SPECIFIEDは少々複雑です.
もし,リソースにアクセスした場合に動的に決定できない場合,
例えば、DBにリソースとカレントユーザとのマッピングがない場合にはアクセスを拒否すべきですが、
NOT_SPECIFIEDを返すことによって、RestrictedResourceのstaticFallbackパラメータがtrueならばアクセスを許可するなど,
静的な認可制御にアクセス許可を引き渡すことができます.
Securing controllers
@RestrictedResourceアノテーションは動的なセキュリティを持つことをソースコードにマークするために使います.
このアノテーションは必須項目として"name"属性をとります.(きめ細かいコントロールが必要ならばそれぞれがユニークである必要があります.)
さらにオプションとして,結果がNOT_SPECIFIED時にアクセスを許可するかどうかのboolean値をとります.
RestrictedResourceアノテーションはクラス、またはメソッドレベルで付加できます.
// This restricts access to the list method according to how your RestrictedResourcesHandler deals with the name "foo". If NOT_SPECIFIED is returned, access is denied.
@RestrictedResource(name = {"foo"})
public static void list()
// This restricts access to the list method according to how your RestrictedResourcesHandler deals with the name "foo". If NOT_SPECIFIED is returned, access is allowed.
@RestrictedResource(name = {"foo"}, staticFallback = true)
public static void list()
// This restricts access to the list method according to how your RestrictedResourcesHandler deals with the name "foo". If NOT_SPECIFIED is returned, access is determined by the Restrict annotation.
@RestrictedResource(name = {"foo"}, staticFallback = true)
@Restrict("foo")
public static void list()
Securing views
ビューのセキュリティを確保するにはdeadbolt.restrictedResourceタグを利用します.
#{deadbolt.restrictedResource resourceKeys:['resourceA']}
this restricts access to this content to how you deal with "resourceA". If NOT_SPECIFIED is returned, access is denied.
#{/deadbolt.restrictedResource}
#{deadbolt.restrictedResource resourceKeys:['resourceA', 'someMetaInfo'], allowUnspecified:false}
this restricts access to this content to how you deal with "resourceA", possibly informed by someMetaInfo. If NOT_SPECIFIED is returned, access is denied.
#{/deadbolt.restrictedResource}
#{deadbolt.restrictedResource resourceKeys:['resourceA'], allowUnspecified:true}
this restricts access to this content to how you deal with "resourceA". If NOT_SPECIFIED is returned, access is allowed.
#{/deadbolt.restrictedResource}
もし,何かしらの情報をパラメータとして渡したい場合は,resourceParameters引数をマップで渡すことによって,RestrictedResourcesHandlerに渡すことができます.
#{deadbolt.restrictedResource resourceKeys:['resourceA'], resourceParameters:['foo':'bar']}
this restricts access to this content to how you deal with "resourceA". A map containing foo -> bar will also be available in the restricted resource handler.
#{/deadbolt.restrictedResource}
Static restrictions(静的な制約)
静的な制約はコントローラやビューにハードコーディングすることで動作します。
Securing controllers
ロールネームはRole.getRoleName()メソッドに対してマッチングを行います.(ロールはRoleHolder#getRoles()から返されます)
コントローラメソッドのアクセス制御を定義するために2つのアノテーションが利用できます。
@Restrict
@Restrictアノテーションはロール名のセットを取り、このセットのAND結合によってコントローラのアクセス権が評価されます。
// This restricts access to the list method to any role holder with a role whose name is "foo"
@Restrict("foo")
public static void list()
// This restricts access to the list method to any role holder with both "foo" and a "bar" roles.
@Restrict({"foo", "bar"})
public static void list()
@Restrictions
@Restrictionsアノテーションは@Restrictsのセットを取り、OR結合によってコントローラのアクセス権が評価されます。
// This restricts access to the list method to any role holder with a role whose name is "foo". This is the equivalent to just @Restrict("foo")
@Restrictions(@Restrict("foo"))
public static void list()
// This restricts access to the list method to any role holder with the "foo" role, or roles whose names are "bar" AND "gee". Note that a role holder with a "bar" OR a "gee" role will be denied access.
@Restrictions({@Restrict("foo"), @Restrict({"bar", "gee"})})
public static void list()
@RoleHolderPresent
@RoleHolderPresentアノテーションはリソースにアクセスするためにはRoleHolderを提供されなければならないことを宣言します(例えば,ユーザがログインしていることを条件にするなど)
このアノテーションの効果は継承されます.
そのため,これを宣言したコントローラを継承したコントローラにも宣言されます.
// This restricts access to the list method to any role holder, regardless of roles.
@RoleHolderPresent
public static void list()
// This restricts access to the Foo controller to any role holder, regardless of roles.
@RoleHolderPresent
public class Foo extends Controller
@Unrestricted
@Unrestrictedアノテーションはコントローラやコントローラメソッドの制限を解除します.
このアノテーションの効果は継承されません.
制限のあるコントローラを継承した場合でもこのアノテーションの効果が発揮されて制約解除されます.
このアノテーションは全ての制約アノテーションよりも優先されます.
// This unrestricts access to the list method, even if the method is in a restricted controller.
@Unrestricted
public static void list()
// This unrestricts access to the Foo controller, even if the parent controller is restricted.
@Unrestricted
public class Foo extends Controller
注意として、
- 無制約コントローラにも制約メソッドを作成できます
- 制約コントローラにも無制約メソッドを作成できます
Securing views
ビューのアクセス制御を定義するにはdeadbolt.restrictタグを利用します.
ビューのアクセス制御は@Restrictionsアノテーションしかないので,コントローラで使われたアノテーションよりもわずかに簡単です.
例えば、ORを表現する場合はトップレベルの配列で、ANDを表現する場合はインナーレベルで表現します.
RoleHoderにマッチした場合のみアクセスが可能になります.
#{deadbolt.restrict roles:[['foo']]}
this restricts access to this content to role holders with the "foo" role
#{/deadbolt.restrict}
#{deadbolt.restrict roles:[['foo', 'bar']]}
this restricts access to this content to role holders with the "foo" AND 'bar' roles
#{/deadbolt.restrict}
#{deadbolt.restrict roles:[['foo'], ['bar']]}
this restricts access to this content to role holders with the "foo" OR 'bar' role
#{/deadbolt.restrict}
もし、ユーザがログインしているかどうかでビューの一部を制限したい場合、
deadbolt.roleHolderPresentタグを利用します.以下がその例です.
#{deadbolt.roleHolderPresent}
this restricts access to this content to logged-in users
#{/deadbolt.roleHolderPresent}
ロールのネゲーション
ネゲーション(ロールを持っていないこと)を条件にする場合、ロールの前に"!"をつけます.
This restricts access to the list method to any role holder with a "foo" role but no "bar" role. If a "bar" role is present, access is denied.
@Restrict({"foo", "!bar"})
public static void list()
ビューでも同様です.
This restricts view rendering for the content to any role holder with a "foo" role but no "bar" role. If a "bar" role is present, rendering is skipped.
#{deadbolt.restrict roles:[['foo', '!bar']]}
...
#{/deadbolt.restrict}
Combining regular and negated role names in views
ビュー内でDeadboltのタグをネストしたり、ネゲーションを活用することで、きめ細かいアクセス制御が可能です.
#{deadbolt.restrict roles:[['foo', 'bar', 'gee']]}
something
#{deadbolt.restrict roles:[['!bar']]}
something only visible to role holders who aren't "bar"s
#{/deadbolt.restrict}
something else
#{/deadbolt.restrict}
Externalised restrictions
Deatboltはコントローラクラスやメソッドに名前付けされた制約ツリー(イメージとしては複数の制約をまとめたもの)のアノテーションとして付加できます。
この機構を静的な制約機構として分類するかは微妙ですが、
静的な機構と同じように動作するため、この静的な制約に分類しています。
@ExternalRestrictions("admin-only")
public static void edit()
@ExternalRestrictions({"admin", "super-mega-admin"})
public static void edit()
このアノテーションは外部制約のマップで表現されます.
ここでもAND, OR, NOTの構文が利用できます.
そして、例えばDBレベルによって変わる標準的なセキュリティ計画を定義することができます.
アノテーションの引数にはアクセス可能な名前を記述します.上記の例ではadminとsuper-mega-adminをがアクセスできます.
ORを利用する場合は、ExternalizedRestrictionのリストをExternalizedRestrictionsに入れてください.
ANDを利用する場合は,ExternalizedRestrictionにロール名のリストを入れてください.
それぞれの名前の前に"!"をつければネゲーションを利用できます.
下記はExternalizedRestrictionsAccessorの実装例です.
import controllers.deadbolt.ExternalizedRestrictionsAccessor;
import models.deadbolt.ExternalizedRestrictions;
import java.util.HashMap;
import java.util.Map;
public class MyExternalizedRestrictionsAccessor implements ExternalizedRestrictionsAccessor
{
private final Map<String, ExternalizedRestrictions> restrictions = new HashMap<String, ExternalizedRestrictions>();
public MyExternalizedRestrictionsAccessor()
{
restrictions.put("standard",
new MyExternalizedRestrictions(new MyExternalizedRestriction("foo"),
new MyExternalizedRestriction("bar")));
restrictions.put("one-role-missing",
new MyExternalizedRestrictions(new MyExternalizedRestriction("foo"),
new MyExternalizedRestriction("rab")));
restrictions.put("admin",
new MyExternalizedRestrictions(new MyExternalizedRestriction("admin")));
restrictions.put("exclude-restricted",
new MyExternalizedRestrictions(new MyExternalizedRestriction("!restricted")));
}
public ExternalizedRestrictions getExternalizedRestrictions(String name)
{
return restrictions.get(name);
}
}
Securing views
ビューに外部制約を設定するにはdeadbolt.externalizedRestrictionタグを利用する。
#{deadbolt.externalizedRestriction externalRestrictions:['admin']}
this restricts access to this content to how you deal with "admin" in your ExternalizedRestrictionsAccessor.
#{/deadbolt.externalizedRestriction}
#{deadbolt.externalizedRestriction externalRestrictions:['admin', 'super-mega-admin']}
this restricts access to this content to how you deal with "admin" OR "super-mega-admin" in your ExternalizedRestrictionsAccessor.
#{/deadbolt.externalizedRestriction}
Mixing annotations
上記で示したアノテーションはクラスやフィールドに組み合わせて適用できます.
組み合わせたときの評価は次の順でANDで評価されます.
- Restrict
- Restrictions
- ExternalRestrictions
Integrating with authentication systems
認証モジュールとDeadboltを一緒に使う例として.sample-and-testsにPlayのSecureモジュールと利用するintegration-with-secureに格納されています.
Caching the RoleHolder on a per-request basis
リクエストスコープ内でロールホルダーが返すロールが変わりうることはまずありえない(そうだったら非決定性のシステムである!)
そのため,キャッシュを用いることでDeadboltHandler#getRoleHolderの呼出しごとにデータベースからロールホルダーを取得する必要はなくなり.リクエストあたりの性能をあげることができます.
キャッシュを有効にするためapplication.confファイルを次のように設定してください.
deadbolt.cache-user-per-request=true
このプロパティが存在しない or true以外の場合、キャッシュは無効になります.
デフォルト動作はキャッシュが無効の動作になります.
Content return types
PlayフレームワークではController#renderメソッドを実行した場合,デフォルトのレスポンスフォーマットはhtmlです.
もし,アプリケーションのほとんど,あるいは一部分で他のフォーマットでレスポンスを返したいかもしれません.
ルートファイルにレスポンスフォーマットを明記したり,クライアントからの指定がない限り,レスポンスフォーマットはHTMLになってしまいます.
実装されたDeadboltHandlerのonAccessFailureメソッドにはそれをチェックする容易な方法はない.
DeadboltHandler#onAccessFailureが実行されたときのデフォルトレスポンスフォーマットを明記するにはapplication.confファイルに次のように書きます.
deadbolt.default-response-format=json
この設定を行った場合,レスポンスタイプが何であっても指定したレスポンスフォーマットになります.
このプロパティに"html"と書けば.htmlのファイルにバインディングし,"json"と書けば.jsonのファイルにバインディングします.
もし,レスポンスタイプの使い分けを行いたい場合,先ほどの設定は役に立たないだろう.
その場合、どのようなレスポンスタイプを使っているかのヒントとなるアノテーションをDeadboltは提供しています.
@JSONと@XMLです.
これらのアノテーションをコントローラや個々のメソッドに適用します.
その場合,DeadboltHandler#onAccessFailureが起動される前にリクエストフォーマットはjsonやxmlにセットされます.
例えば次のように記述します.
@JSON
public static void response2json() {
・・・
注意として、@JSONを適用したコントローラの中で@XMLを適用したメソッドを作成することができます(もちろん逆も)
次世代のステキなフォーマットが発明されたらどうするか?
そのときは,あなた自身の手で新しいアノテーションを実装すれば解決します.
作成したアノテーションをコントローラやメソッドに適用することでonAccessFailureのrenderの間でチェックできます.
ひとつ重要なこととして、onAccesssFailureから実行したforbiddenメソッドがリダイレクトできない場合,
最も簡単な方法として、403ファイルをviews/errorsディレクトリに作成し.Deadbolt.forbidden()を実行する方法があります.
その方法例はsamples-and-tests/content-typesに格納してあります.
理想的には、コンテントタイプはルートファイルに明記するべきだが.
ルートファイルのワイルドカードは非常に有用であるため,実装でカバーする必要があります.