Apache Shiroのチュートリアルの一部でAuthorizationに関する部分の https://shiro.apache.org/java-authorization-guide.html をテキトーに訳した。
Java Authorization Guide with Apache Shiro
認証もしくはアクセス制御とは、リソースへのアクセス権を特定する機能です。つまり、誰が何にアクセスするのか、ということです。
認証チェックの例を挙げると、ユーザに対し、webページの閲覧・データ編集・ボタンの可視性・プリンターへの出力、を許可するかどうかをチェックします。これらはすべて、ユーザが何にアクセスできるかどうかを決定しています。
Elements of Authorization
認証には三つの中核要素があり、パーミッション・ロール・ユーザで、これらはShiroでは頻繁に参照します。
Permissions Defined
パーミッションはセキュリティポリシーのうち最もアトミックなレベルで、 パーミッションは機能性の表明です*1。パーミッションはアプリケーションで何が可能なのかの表現です。well formed*2なパーションはリソースタイプと、そのリソースとの相互作用時に可能なアクションを記述しています。ドアを開けられるか、ファイル読み込み可能か、顧客データを削除可能か、ボタンを押せるか、など。
データ系リソースで一般的なアクションは、作成・読み込み・更新・削除で、いわゆるCRUDです。
重要な点は、パーミッションは誰がそのアクションを実行可能かを持つのではなく、何に対してそのアクションが実行可能かを表明する、を理解することです。
Levels of permission granularity
上で挙げたパーミッションはすべて、リソース(ドア・ファイル・データレコードなど)に対する、アクション(開く・読み込み・削除など)を定義しています。Shiroでは、任意の深さでパーミッションを定義可能です。以下はいくつかの一般的なパーミッションレベルを粒度(granularity)順に示したものです。
- リソースレベル(Resource Level) - これは最も一般的で構築するのも容易です。例えば、ユーザは顧客データを編集したりドアを開けたり出来ます。リソースは定義されていますが、そのリソースの特定のインスタンスは定義しません。
- インスタンスレベル(Instance Level) - パーミッションはリソースのインスタンスを定義します。例えば、ユーザはIBMの顧客データを編集したりキッチンのドアを開けたり出来ます。
- アトリビュートレベル(Attribute Level) - パーミッションはインスタンスもしくはリソースのアトリビュート(属性)を定義します。例えば、ユーザはIBMの顧客データの住所を編集出来ます。
パーミッションに関する詳細はPermissions Documentationを参照してください。
Roles Defined
認証のコンテキストでは、ロールは実質的にはパーミッションの集合で、その集合はパーミッションとユーザの管理を単純化するのに使われます。パーミッションを直接割り当てるのではなく、ユーザにロールを割り当てられます。多数のユーザを抱えていたり複雑なアプリケーションでは、パーミッションはややこしくなりがちです。例としては、銀行アプリケーションでは管理者(administrator)ロールや出納係(bank teller)ロールが挙げられます。
ロールには二種類あり、Shiroは両方ともサポートします。
Implicit Roles
ほとんどの人はロールを、アプリケーションがパーミッションの組み合わせを暗示するような暗黙的なロール(implicit role)という形を取る、と理解しています。そうなる理由は、明示的なパーミッションが付与されたロールではなく、ユーザが特定のロールを持つか、あるいはアプリケーションでそれらのパーミッションをチェックしているためです。コードでのロールチェックは基本的に暗黙的なロールの反映となります。あるユーザは患者データを閲覧(view patient data)可能で、それは管理者(administrator)ロールを持つからです。あるユーザはアカウントを作成(create an account)可能で、それは出納係(bank teller)ロールを持つからです。こうした役割などの名称が存在することと、ソフトウェアが実際に実行可能なことの間に、相関性はありません。たいていの人はこのやり方でロールを使用します。使うのが簡単ではあるものの、ごく単純なアプリケーションを除いて、メンテナンスの手間と管理上の問題を多数生み出します。
Explicit Roles
明示的なロール(explicit role)は明示的に付与されたパーミッションを持ちます。よって、そのロールはパーミッションの明示的な集合になります。コード中のパーミッションチェックは明示的なロールの反映となります。あるユーザは患者データを閲覧(view patient data)可能で、それは管理者ロールの一部であるview patient dataパーミッションを持つからです。あるユーザはアカウントを作成(create an account)可能で、それは出納係ロールの一部であるcreate accountパーミッションを持つからです。ユーザがこれらのアクションを実行可能なのは、文字列ベースの暗黙的なロール名を持つからではなく、そのアクションに対応するパーミッションがロールに明示的に付与されているからです。
明示的なロールの大きな利点は管理の容易さとアプリケーションのメンテナンスコストを低下させることです。ロールを追加・削除・変更する必要が出てきたとしても、ソースコードを修正する必要はありません。また、Shiroでは、実行時に動的にロールを追加・削除・変更することが可能で、認証チェックは常に最新の値を用います。つまり、新しいパーミッションのためにユーザにログアウト&ログインを強いる必要がない、ということです。
Users Defined
アプリケーションを使うのは誰かといえばユーザです。しかし、Shiroではユーザの概念は実際にはSubjectインスタンスになります。ユーザは基本的には人間を指し、ShiroのSubjectは人間かサービスかどうかを問わずアプリケーションと相互作用するモノになるので、ユーザの代わりにSubjectという単語を使います。
ロールもしくは直接パーミッションを付与することでアプリケーションで特定のアクション実行をユーザが可能となります。よって、あるユーザが顧客データを開けるのはopen customer recordパーミッションが付与されているから、となります。パーミッション付与は、ロールをユーザに割り当てるか、直接パーミッションを割り当てます。
ユーザもといサブジェクトの詳細についてはSubject Documentationを参照してください。
Note
Realmの実装は何らかのデータソース(RDBMS, LDAPなど)と通信するものになります。よって、そのレルムがロールやパーミッションが存在するかどうかをShiroに伝えることになります。認証モデルの動作はすべて制御が可能です。
How to perform Authorization in Java with Shiro
Shiroでの認証の利用は4通りのやり方があります。
- プログラム -
if
とelse
ブロックなどJavaのコードで認証チェックを実行する。 - JDKアノテーション - Javaのメソッドに認証アノテーションを付与する。
- JSP/GSP Taglibs - jspないしgspページの出力をロールとパーミッションで制御する。
Programmatic Authorization
認証処理のパーミッションとロールのチェックをJavaのコードで行うのは古くからあるやり方です。以下はShiroでパーミッションやロールのチェックを行う方法の解説です。
Role Check
以下のサンプルはアプリケーションでロールチェックをするコードの例です。いま、あるユーザがadministratorロールを持つかどうかをチェックし、持っていれば専用ボタンを表示し、そうでなければ表示しない、ようにしたいとします。
まず、現在のユーザのSubjectを取得します。それから、Subjectの.hasRole()
メソッドにadminstratorを渡します。このメソッドはTRUE
かFALSE
を返します。
//Subjectを取得 Subject currentUser = SecurityUtils.getSubject(); if (currentUser.hasRole("administrator")) { //専用ボタンを表示 } else { //専用ボタンを表示しない(か、それ以外の処理?) }
ロールベースのチェックはすぐに実装出来て簡単ですが大きな欠点があり、それは暗黙的になる点です。
後からロールを追加・削除・再定義したい場合に何が起きるでしょうか。ソースコードをすべて調べて、セキュリティモデルの変更を反映するようにすべてのロールチェックを修正する必要が出てきます。アプリケーションをシャットダウンして、ソースコードを調査して、テストして、再起動が必要となります。
極めて単純なアプリケーションであればこれでも十分ですが、大規模アプリケーションではアプリケーションが使われ続ける限り重大なトラブルの種となり、ソフトウェアのメンテナンスコストが高くつき続けることになります。
Permission Check
以下のサンプルはパーミッションを用いてセキュリティチェックをするコードの例です。いま、あるユーザがlaserjet3000n*3にプリントアウトするパーミッションを持つかどうかをチェックし、持っていればプリントボタンを表示し、そうでなければ表示しない、ようにしたいとします。以下はインスタンスレベルパーミッションもしくはインスタンスレベル認証の例です。
先の例と同じく、まず現在のユーザのSubjectを取得します。それから、Permission
オブジェクトか、リソースに対するアクションを表すインスタンスを生成します。この例の場合、printerPermission
というインスタンスがあり、リソースに相当するのがlaserjet3000nで、アクションはprintです。その次に、サブジェクトの.isPermitted()
メソッドにprinterPermission
を渡します。このメソッドはtrueかfalseを返します。
Subject currentUser = SecurityUtils.getSubject(); Permission printPermission = new PrinterPermission("laserjet3000n","print"); If (currentUser.isPermitted(printPermission)) { //何らかの処理(プリントボタンの表示など) } else { //ボタンを非表示にするなど。 }
Permission Check (String-based)
パーミッションクラスではなく単純に文字列を用いてパーミッションチェックをすることも出来ます。
Shiroのpermission interfaceの実装がひとまず面倒な場合はただ単に文字列を渡します。この例の場合、.isPermitted()
メソッドに文字列printer:print:LaserJet4400n
を渡します。
String perm = "printer:print:laserjet4400n"; if(currentUser.isPermitted(perm)){ //何らかの処理(プリントボタンの表示など) } else { //ボタンを非表示にするなど。 }
何らかのパーミッションと連動する自前のRealmを用意すれば、後は個々の実装でパーミッション文字列を構築します。このチュートリアルではShiroのパーミッションシンタックスWildCardPermissionsを使用しています。WildCardPermissionsは強力で直感的に使えます。詳細についてはPermissions Documentationをチェックしてください。
文字列ベースパーミッションチェックで出来ることはそうでない場合と変わりません。これの利点は、permission interfaceの実装をせずに済むのと、ただ単に文字列でパーミッションを構築できる点です。欠点は、タイプセーフではなくなり、将来的に文字列ベースでは表現が困難な複雑なパーミッション機能が必要となった場合、permission interfaceを実装するパーミッションオブジェクトを作っておけば良かった、になると思われます。
Annotation Authorization
コードによる認証チェックを書きたくない場合はアノテーションを使います。Shiroはメソッドに付与するJava annotationsをいくつか提供しています。
Enabling Annotation Support
ShiroのJavaアノテーションを使い始める前にアプリケーションでAOPサポートを有効化する必要があります。AOPフレームワークは複数存在しますが、残念なことに、アプリケーションでAOPを有効化する標準的な方法はありません。
AspectJについては、AspectJ sample applicationを参照してください。
Springについては、Spring Integrationドキュメントを参照してください。
Guiceについては、Guice Integrationドキュメントを参照してください。
Permission Check
以下の例では、openAccount
メソッドの実行前にそのユーザがaccount:create
パーミッションを持つかをチェックしています。もしチェックOKであればメソッドは期待通り呼び出され、そうでない場合は例外がスローされます。
コードを書いてのチェックと同様に、アノテーションに指定するものはPermissionオブジェクトか、単なる文字列を使う方法があります。
//呼び出し側のロールがどれもAccountの'create'パーミッションに該当しない場合 //AuthorizationExceptionをスローします。 @RequiresPermissions("account:create") public void openAccount( Account acct ) { //アカウントを作成する。 }
Role Check
以下の例では、openAccount
メソッドの実行前にそのユーザがteller
ロールを持つかをチェックしています。もしチェックOKであればメソッドは期待通り呼び出され、そうでない場合は例外がスローされます。
//呼び出し側が'teller'ロールを持たない場合はAuthorizationExceptionをスロー //します。 @RequiresRoles( "teller" ) public void openAccount( Account acct ) { //tellerだけが実行するべき何らかの処理。 }
JSP TagLib Authorization
JSP/GSPベースのwebアプリケーション用のtag libraryをShiroで用意しています。
以下の例では、users:manageパーミッションを持つユーザに対してはManage Usersページへのリンクを表示しています。このパーミッションを持たない場合は適当なメッセージを表示します。
この機能を使うにはまず、Shiroのtablibをwebアプリケーションに追加します。次に、users:manageのチェックに用いる<shiro:hasPermission>
タグを追加します。<shiro:hasPermission>
タグ内に、ユーザがそのパーミッションを持つ場合に実行したいコードを記述します。逆に、ユーザがそのパーミッションを持たない場合に何らかの処理をしたい場合、<shiro:lacksPermission>
タグを追加します。その際は再度users:manageのチェックを書きます。
<%@ taglib prefix="shiro" uri=http://shiro.apache.org/tags %> <html> <body> <shiro:hasPermission name="users:manage"> <a href="manageUsers.jsp"> Click here to manage users </a> </shiro:hasPermission> <shiro:lacksPermission name="users:manage"> No user management for you! </shiro:lacksPermission> </body> </html>
また、ロールやその他のユーザデータ・状態をチェックするタグもあります。
JSP/GSP Tagsの詳細についてはJSP Tag Libraryを参照してください。また、webアプリケーションとの統合サポートについてはWeb Integration Documentationを参照してください。
Caching Authorization
Lend a hand with documentation
Apache Shiroで何かを作る際にこのドキュメントが役に立つことを願いますが、コミュニティが成長すればドキュメントも常に拡張していきます。もしShiroプロジェクトに興味がある場合、必要だと感じたドキュメントの追加・修正・収集をお願いしたいと思います。ほんの少しの助けであってもコミュニティは前進し、結果としてShiroも前進します。
ドキュメントをコントリビュートする最も簡単な方法はこのページ下のEditリンクをクリックしてプルリクエストをサブミットするか、User ForumあるいはUser Mailing Listに送信します。