2014年2月23日日曜日

Apache Shiro Configuration (日本語訳)

# Apache Shiro Configuration の日本語訳

  単純なコマンドライン・アプリケーションからクラスター化された大規模エンタープライズアプリケーションに至るまで、あらゆる環境で動作するように Shiro は設計されています。環境は多岐にわたるため、適切に設定をおこなえるよう、設定方法も数多く存在します。この節では、Shiro core によりサポートされる設定方法について説明します。

多くの設定オプション

  Shiro の SecurityManager 実装群と補助コンポーネント群は、全て JavaBeans 互換です。これにより、事実上 Shiro は、通常の Java、XML (Spring, JBoss, Guice, etc), YAML, JSON, Groovy Builder マークアップ、等々、設定書式を問わずに設定が可能となっています。


プログラムによる設定

  SecurityManager を作成してアプリケーションで利用可能にするための絶対的に一番簡単な方法は、コード内で org.apache.shiro.mgt.DefaultSecurityManager を作成してつなぐ方法です。

例:
// Realm インスタンスを初期化するかどこかから取得する。
// Realm については後述します。
Realm realm = ...;

// SecurityManager のインスタンスを作成します。
SecurityManager securityManager = new DefaultSecurityManager(realm);

// static メモリーを介してアプリケーション全体から
// SecurityManager インスタンスを利用できるようにします。
SecurityUtils.setSecurityManager(securityManager);

  驚くべきことに、たった三行のコードで、多くのアプリケーションにとって十分に機能する Shiro 環境が用意できました。とても簡単だったでしょう!?


SecurityManager オブジェクトグラフ

  アーキテクチャーの章で説明したように、Shiro の SecurityManager 実装群はその本質として、セキュリティーに特化したコンポーネント群がネストした、モジュール式オブジェクトグラフです。コンポーネント群もまた JavaBeans 互換であるため、SecurityManager およびその内部オブジェクトグラフを設定するのに、ネストされたコンポーネント群の getter メソッドと setter メソッドを呼ぶことができます。

  例えば、セッション管理をカスタマイズするため、カスタム仕様の SessionDAO を使うように SecurityManager を設定したいのであれば、ネストしている SessionManagersetSessionDAO メソッドを用い、SessionDAO を直接セットすることができます。

...

// セキュリティーマネージャー
DefaultSecurityManager securityManager
   = new DefaultSecurityManager(realm);

// セッション DAO
SessionDAO sessionDAO
   = new CustomSessionDAO();

// セッション DAO を直接設定する。
((DefaultSessionManager)securityManager.getSessionManager())
    .setSessionDAO(sessionDAO);

...

  直接メソッドを起動することで、SecurityManager のオブジェクトグラフのあるゆる箇所を設定することができます。

  しかし、プログラムによるカスタマイズが単純だとしても、それが実世界のほとんどのアプリケーションにとって理想の設定方法というわけではありません。プログラムによる設定がアプリケーションにとって不適切となりうる理由が幾つかあります。

  • 実装に関する知識を有することと実装を初期化することが求められます。 具体的な実装に関することや、それがどこに存在するのかについて気にしなくてもいいのであれば、そちらのほうが良いでしょう。
  • Java の型安全性のため、get* メソッドで取得したオブジェクトを個別の実装へとキャストすることが求められます。 キャストが多いのは醜く、冗長であり、実装クラスに強く縛られてしまいます。
  • SecurityUtils.setSecurityManager メソッドでは、初期化された SecurityManager インスタンスを JVM の static なシングルトンにしてしまいます。多くのアプリケーションではそれでいいかもしれませんが、同一 JVM 上で複数の Shiro アプリケーションが実行されるようなことがあれば、問題が発生します。個々のアプリケーションの中でシングルトンではあるものの static なメモリー参照ではない、というのであれば、そちらのほうが良いでしょう。
  • Shiro の設定を変更するたびにアプリケーションの再コンパイルが求められます。

  とはいえ、このような欠点があるものの、直接プログラムで操作する方法は、例えばスマートフォンアプリケーションのようなメモリに制約のある環境では依然として価値があります。メモリに制約のある環境でアプリケーションを実行するのでなければ、テキストによる設定のほうが、使いやすく、また読みやすいでしょう。


INI 設定

  ほとんどのアプリケーションでは、ソースコードに影響を与えることなく変更できるテキストベースの設定により恩恵を受けることができます。また、Shiro の API に詳しくない人でも、理解しやすいという利点もあります。

  サードパーティーへの依存を最小限にとどめ、全ての環境で機能する公約数的なテキストベース設定メカニズムを確実に提供するため、Shiro は、SecurityManager オブジェクトグラフおよび補助コンポーネントを構築するための INI フォーマットをサポートしています。INI は読みやすく、設定しやすく、準備も単純で、ほとんどのアプリケーションに適しています。


INI から SecurityManager を生成する

  INI 設定に基づいて SecurityManager を構築する例を二つ挙げます。


INI リソースによる SecurityManager

  INI リソースパスから SecurityManager インスタンスを生成することができます。リソースは、file:, classpath:, url: というプレフィックスにより、ファイルシステム、クラスパス、URL から取得することができます。この例では、クラスパスのルートから shiro.ini を読み込んで SecurityManager インスタンスを得るためにファクトリークラスを使用しています。

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.IniSecurityManagerFactory;

...

// クラスパスのルートから shiro.ini を読み込む。
Factory<SecurityManager> factory
    = new IniSecurityManagerFactory("classpath:shiro.ini");

// ファクトリーから SecurityManager を構築する。
SecurityManager securityManager = factory.getInstance();

// SecurityManager をセットする。
SecurityUtils.setSecurityManager(securityManager);


INI インスタンスによる SecurityManager

  お望みであれば、org.apache.shiro.config.Ini クラスから INI 設定をプログラムにより構築することもできます。Ini クラスは JDK の java.util.Properties クラスと同じような動作をしますが、セクション名による分割をサポートしています。

例:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.Ini;
import org.apache.shiro.config.IniSecurityManagerFactory;

...

Ini ini = new Ini();

// 必要に応じて Ini インスタンスを設定する。
...

// Ini インスタンスからファクトリーインスタンスを作る。
Factory<SecurityManager> factory = new IniSecurityManagerFactory(ini);

// ファクトリーから SecurityManager を構築する。
SecurityManager securityManager = factory.getInstance();

// SecurityManager をセットする。
SecurityUtils.setSecurityManager(securityManager);

  INI 設定から SecurityManager を構築する方法はわかったので、Shiro INI 設定の定義方法をみていきましょう。


INI セクション

  INI は基本的に、一意の名前を持つセクション群で構成される、キー/値の組によるテキストベースの設定です。キーは各セクション内では一意ですが、設定全体で一意ではありません(この点は JDK Properties とは異なります)。とはいえ、各セクションは一つの Properties 定義のように見えます。

  コメント行はオクトソープ(# - ハッシュ、パウンド、ナンバーとも呼ばれます)もしくはセミコロンで始めます。

Shiro が解釈するセクションの例:
# =======================
# Shiro INI 設定
# =======================

[main]
# オブジェクトとそのプロパティーをここで定義します。
# securityManager や Realm、その他 SecurityManager を
# 構築するのに必要なもの。


[users]
# users セクションは、限られた数のユーザーアカウント群を
# 固定的に用意する必要があるときに使います。


[roles]
# roles セクションは、限られた数のロール群を固定的に
# 用意する必要があるときに使います。


[urls]
# urls セクションは、Web アプリケーションにおいて、
# URL ベースのセキュリティーのために使用します。



[main]

  [main] セクションは、アプリケーションの SecurityManager インスタンスおよびそれが依存するコンポーネント(Realm など)を設定する場所です。

  SecurityManager やそれが依存するコンポーネントといったオブジェクトインスタンスを INI で設定するのは、名前/値の組しか使えないことを考えると、難しそうに思えます。しかし、ちょっとした決まり事とオブジェクトグラフに関する理解があれば、かなり多くのことができることが分かるでしょう。Shiro では、単純ではあるけれども極めて簡潔な設定メカニズムを可能とするため、このような想定をしています。

  我々はよく、このアプローチを「貧者の」依存性注入 ("poor man's" Dependency Injection) と好んで呼んでいます。Spring, Guice, JBoss XML の本格的な依存性注入メカニズムほど強力ではないものの、複雑さを排しながらも多くのことが実現できることにあなたも気付くことでしょう。もちろん他の設定方法も利用可能ですが、Shiro を使う上では必要とされません。

  あなたの欲求を刺激するため、有効な [main] 設定の例を挙げます。詳細はこれから説明していきますが、「直感だけでも行われていることの大半を理解できる」と感じるのではないでしょうか。

[main]
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher

myRealm = com.company.security.shiro.DatabaseRealm
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
myRealm.password = secret
myRealm.credentialsMatcher = $sha256Matcher

securityManager.sessionManager.globalSessionTimeout = 1800000


オブジェクトを定義する

  [main] セクションの次の箇所を見てみましょう。

[main]
myRealm = com.company.security.shiro.DatabaseRealm

  この行では com.company.shiro.realm.MyRealm という型の新しいオブジェクトインスタンスを生成し、myRealm という名前を付けて以降の設定で参照できるようにしています。

  インスタンス化されたオブジェクトが org.apache.shiro.util.Nameable インターフェースを実装している場合、Nameable.setName メソッドが名前の(この例では myRealm)を引数として実行されます。


オブジェクトのプロパティーを設定する

<プリミティブ値>

  単純なプリミティブプロパティーは、等号記号を使うだけで設定できます。

...
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
...

  これらの設定行は次のようなメソッド呼び出しへと変換されます。

...
myRealm.setConnectionTimeout(30000);
myRealm.setUsername("jsmith");
...

  どうしてこんなことが可能なのかですかって? 全てのオブジェクトが Java Beans 互換の POJO であることを前提としているからです。

  Shiro の内部実装は、デフォルトでは、プロパティーをセットするときの大変な処理を全て Apache Commons BeanUtils を使っておこなっています。そのため、INI 値はテキストですが、BeanUtils は文字列値を適切なプリミティブ型に変換する方法と、対応する JavaBeans の setter メソッドを呼び出す方法を知っているのです。


<参照値>

  セットする値がプリミティブではない、何らかのオブジェクトの場合はどうなるでしょうか? はい、その場合はドル記号($)を使い、定義済みのインスタンスを参照してください。

例:
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
myRealm.credentialsMatcher = $sha256Matcher
...

  これは単純に sha256Matcher という名前で定義されているオブジェクトを探し、BeanUtils を使って myRealm インスタンスにそのオブジェクトを(myRealm.setCredentialsMatcher(sha256Matcher) メソッドを呼んで)セットします。


<ネストしたプロパティー>

  INI 行の等号記号の左側でドット記法を使い、オブジェクトグラフをたどって最終的なオブジェクト/プロパティーを取得することができます。例えば、この設定行は

...
securityManager.sessionManager.globalSessionTimeout = 1800000
...

(BeanUtils により)下記のように変換されます。

securityManager.getSessionManager().setGlobalSessionTimeout(1800000);

  グラフは必要なだけ深くたどっていくことができます。

object.property1.property2....propertyN.value = blah

BeanUtils プロパティーサポート

BeanUtils.setProperty メソッドがサポートするプロパティー代入操作は、set/list/map 要素の代入を含め、全て Shiro の [main] セクションで機能します。より詳しい情報は Apache Commons BeanUtils のウェブサイトとドキュメントを参照してください。


<バイト配列値>

  バイト配列をそのままテキストフォーマットで指定することはできないので、バイト配列をテキストに符号化しなければなりません。値は、Base64(デフォルト)もしくは十六進数で符号化した文字列として指定することができます。Base64 がデフォルトなのは、Base64 による符号化が値を表現するのにより少ないテキストで済むからです。符号化に使用するアルファベット集合がより多く、つまり、トークンが短くなるのです(テキストによる設定にとってはちょっと好都合です)。

# cipherKey 属性はバイト配列。デフォルトでは、バイト配列属性に
# 対応するテキストは Base64 で符号化されているものと想定されます。

securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
...

  しかし、かわりに十六進数による符号化を使いたいというのであれば、文字列トークンの先頭に 0x (「ゼロ」と「x」)を付けてください:

securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008


<集合プロパティー>

  List, Set, Map も他のプロパティーと同様、直接またはネストしたプロパティーとしてセットすることができます。SetList は、オブジェクトの値をカンマで区切りで並べます。

SessionListener を複数指定する例:
sessionListener1 = com.company.my.SessionListenerImplementation
...
sessionListener2 = com.company.my.other.SessionListenerImplementation
...
securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2

  Map については、キーと値をコロンで連結した組をカンマ区切りで並べます。

object1 = com.company.some.Class
object2 = com.company.another.Class
...
anObject = some.class.with.a.Map.property

anObject.mapProperty = key1:$object1, key2:$object2

  上記の例では、$object1 で参照されるオブジェクトが String のキー key1map に格納されます。つまり、map.get("key1")object1 を返します。他のオブジェクトをキーとして使うこともできます。

anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
...


留意事項

<順序が意味を持つ>

  ここまでに述べた INI フォーマットとその決まり事は、とても使い勝手が良く、理解も容易です。しかし、他の XML ベースの仕組みほど強力ではありません。上記の仕組みを使う上で、理解しなければならない最も重要なことは、順序が意味を持つ、ということです!

慎重に

  オブジェクトの生成と値の代入は、[main] セクションに現れた順番で実行されます。各行は最終的に JavaBeans の getter/setter メソッドに変換され、同じ順序で実行されます! 設定を書くときにはこの点を覚えておいてください。


<インスタンスの上書き>

  どのオブジェクトも、設定内で後から定義された新しいインスタンスで上書きすることができます。そのため、例えば二番目の myRealm の定義は一番目のものを上書きします。

...
myRealm = com.company.security.MyRealm
...
myRealm = com.company.security.DatabaseRealm
...

  これにより、myRealmcom.company.security.DatabaseRealm のインスタンスとなり、以前のインスタンスが使われることはありません(ガベージコレクションの対象となります)。


<デフォルト SecurityManager>

  ここまでの完全な例の中で SecurityManager インスタンスのクラスが定義されておらず、ネストしたプロパティーをいきなり設定していたことに気が付かれたかもしれません:

myRealm = ...

securityManager.sessionManager.globalSessionTimeout = 1800000
...

  これは、securityManager インスタンスが特別なものだからです。既に生成済みで準備ができており、どの SecurityManager 実装クラスをインスタンス化すべきかを設定者が知る必要はありません。

  もちろん、独自の実装を実際に指定したい場合には、そうすることができます。「<インスタンスの上書き>」で説明したように、独自の実装を定義してください:

...
securityManager = com.company.security.shiro.MyCustomSecurityManager
...

  もちろん、この手順が必要となることは滅多にないでしょう。Shiro の SecurityManager の実装はカスタマイズ性が高く、通常必要となるものは設定可能だからです。本当に必要かどうか、自分自身に(またはユーザーの方々に)尋ねてみたいと思うかもしれません。


[users]

  [users] セクションでは、ユーザーアカウントを固定的に定義することができます。ユーザーアカウントの数が限られる環境や、ユーザーアカウントを実行時に動的に生成する必要のない環境では、大抵便利に使うことができます。

[users]
admin = secret
lonestarr = vespa, goodguy, schwartz
darkhelmet = ludicrousspeed, badguy, schwartz

自動的な IniRealm

  空ではない [users] セクションもしくは [roles] セクションを定義すると、自動的に org.apache.shiro.realm.text.IniRealm のインスタンスが生成され、[main] セクション内で iniRealm という名前で参照することができます。これまで説明した方法と同じように、当該インスタンスを設定するこが可能です。


行フォーマット

  [users] セクションの各行は次の書式に従っていなければなりません。

ユーザー名 = パスワード, ロール名1, ロール名2, ..., ロール名N
  • 等号記号の左側の値はユーザー名です。
  • 等号記号の右側の最初の値はユーザーのパスワードです。パスワードの指定は必須です。
  • パスワードに続くカンマ区切りの値は、ユーザーに割り当てるロール名です。ロール名の指定は任意です。

パスワードの暗号化

  [users] セクションのパスワードを平文にしたくないのであれば、好きなハッシュアルゴリズム(MD5, SHA-1, SHA-256, 等)を使って暗号化し、結果として得られた文字列をパスワードとして使うことができます。デフォルトでは、パスワード文字列は十六進数で符号化されているものと想定されますが、かわりに Base64 での符号化を用いるように設定することもできます(以下に説明します)。

簡単で安全パスワード

  無駄な時間をかけずにベストプラクティスを実践するため、パスワードおよび他のどんなタイプのリソースのハッシュ値でも計算することができる Shiro のコマンドラインハッシュ計算プログラムを使いたいと思うかもしれません。INI [users] パスワードの暗号化をおこなうには特に便利です。

  ハッシュ化したパスワードをテキストで設定したあとは、暗号化されていることを Shiro に伝えなければなりません。これは、[main] セクションで暗黙的に生成された iniRealm が、あなたの使用したハッシュアルゴリズムに対応する適切な CredentialsMatcher の実装を使うように設定することでおこないます。

[main]
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
iniRealm.credentialsMatcher = $sha256Matcher
...

[users]
# user1 = SHA-256 でハッシュ化したパスワードの十六進数表現, role1, role2, ...
user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b, role1, role2, ... 

  他のオブジェクトと同様、使用するハッシュストラテジーに合わせて、CredentialsMatcher を設定することができます。例えば、ソルト処理を使用するかどうかや、ハッシュ処理を何回繰り返すか、ということを設定できます。これらの設定項目があなたにとって有用であるならば、ハッシュストラテジーをより深く理解するため、org.apache.shiro.authc.credential.HashedCredentialsMatcher の JavaDoc を参照してください。

  例えば、users のパスワード文字列をデフォルトの十六進数表記ではなく Base64 表記にするのであれば、次のように指定します。

[main]
...
# true = 十六進数, false = Base64
sha256Matcher.storedCredentialsHexEncoded = false


[roles]

  [roles] セクションでは、[users] セクションで定義したロールにパーミッションを関連付けます。もう一度言いますが、これは、ロールの数が限られているか、ロールを実行時に動的に生成する必要がない環境において便利です。例を挙げます。

[roles]
# admin ロールは全てのパーミッションを持ちます。
# ワイルドカード '*' が全てのパーミッションを意味します。
admin = *

# schwartz ロールは lightsaber については何でもできます。
schwartz = lightsaber:*

# goodguy ロールは、eagle5 というナンバープレート
# (インスタンス固有の ID) を持つ winnebago (型) を
# drive (アクション) することを許可されています。
goodguy = winnebago:drive:eagle5


行フォーマット

  [roles] セクションの各行は、ロールからパーミッション群への対応を、次のような形式で定義しなければなりません。

ロール名 = パーミッション定義1, パーミッション定義2, ..., パーミッション定義N

  ここで「パーミッション定義」は任意の文字列ですが、ほとんどの人は、使いやすさと柔軟性のため、org.apache.shiro.authz.permission.WildcardPermission の書式に従う文字列を使おうと考えます。パーミッションに関する詳細情報と、それがどのように役に立つのかについては、パーミッションのドキュメントを参照してください。

内部カンマ

  もしも個々のパーミッション定義内でカンマ区切りを用いたい場合(例えば printer:5thFloor:print,info など)、パースエラーを避けるため、定義をダブルクォーテーションで囲む必要があります。

"printer:5thFloor:print,info"

パーミッションの無いロール

  パーミッションに関連付ける必要のないロールは、そうしたくないのであれば [roles] セクションに書く必要はありません。まだ存在しないロールを生成するだけであれば、[users] セクションにロール名を定義するだけで十分です。


[urls]

  このセクションとそのオプションについては Web に関する章で説明します。


文書のご協力をお願いします

  本文書が Apache Shiro を扱う作業の手助けとなることを望みつつ、コミュニティーでは常に文書の改良と拡張をおこなっています。Shiro プロジェクトを手伝ってくださるのであれば、必要だと思われた箇所から文書の訂正、拡張、追加のご検討をお願いします。どんなに小さなことであっても、あなたの貢献は全て、コミュニティーの発展、そして Shiro の改善につながります。

  文書による貢献の最もやり易い方法は、ユーザーフォーラムもしくはユーザーメーリングリストに文書を送付いただくことです。