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 の改善につながります。

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


2013年12月29日日曜日

Apache Shiro Realms (日本語訳)

# Apach Shiro Realms の日本語訳

  Realm は、ユーザー、ロール、パーミッション等のアプリケーション固有のセキュリティーデータにアクセスするコンポーネントです。データソースが幾つ存在していようが、どれだけアプリケーションに特化したデータであろうが、アプリケーション固有のデータが Realm を通して解釈可能なフォーマットに整えられ、それにより Shiro は、単一でかつ理解も容易な Subject というプログラミング API を提供することが可能となっています。

  Realm は通常、リレーショナルデータベースや LDAP ディレクトリ、ファイルシステムといったデータソース、もしくは他の似たようなリソースと、1 対 1 の関係を持っています。そのため、Realm インターフェースの実装では、JDBC やファイル I/O、Hibernate、JPA、その他のデータアクセス API などのデータソース固有の API を用いて、認可情報 (ロールやパーミッションなど) を取得します。

Realm の本質は、セキュリティーに特化した DAO です。

  これらのデータソースは通常、認証データ (パスワードなどの認証情報) と認可データ (ロールやパーミッション等) の両方を保持しているため、全ての Realm は認証処理と認可処理の両方をおこなうことができます。


Realm の設定

  Shiro の INI 設定を使用するのであれば、他のオブジェクトと同様、[main] セクションで Realm を定義したり参照したりすることができます。ただし、securityManager に設定する際には、明示的におこなうか暗黙的におこなうかのどちらか一方の方法をとることになります。


明示的な代入

  INI 設定に関してこれまでに学んだことを考えれば、この設定方法は当然とも言うべきアプローチです。一つ以上の Realm を定義したあと、securityManager のコレクションプロパティーにセットします。

例:
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm

securityManager.realms = $fooRealm, $barRealm, $bazRealm

  明示的な代入では、設定内容が明確に定まります。認証と認可にどの Realm を使うかという点と併せて、Realm 間の順序をどうするか、も正確に制御することができます。Realm の順序の効果は、認証の章「認証シーケンス」で詳しく説明されています。


暗黙的な代入

非推奨

  暗黙的な代入では、Realm の定義順序が変わることで、想定外の動きになってしまうことがありえます。 この設定方法を避け、設定内容を明確に定めることができる明示的な代入を使うことを推奨します。 暗黙的な設定は、Shiro の将来のリリースで deprecated 扱いになるか削除される可能性があります。

  なんらかの理由で securityManager.realms プロパティーを明示的に設定したくない場合は、Shiro に、定義済みの全ての Realm を見つけさせ、それらを直接 securityManager に設定させることもできます。

  この方法では、 Realm は定義された順番に securityManager に設定されていきます。

  つまり、例えば shiro.ini で下記のようになっていたすると、

blahRealm = com.company.blah.Realm
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm

# no securityManager.realms assignment here

基本的には、次の行が追加されているのと同じ効果があります。

securityManager.realms = $blahRealm, $fooRealm, $barRealm

  暗黙的な代入を用いると、認証や認可の試行時に Realm に対してどのように問い合わせがおこなわれるかは、Realm が定義される順番に直接影響を受ける、ということを認識しておいてください。定義順序を変えると、Authenticator の認証シーケンスが変化します。

  このような理由から、 また動作を明確化するためにも、暗黙的な代入ではなく明示的な代入のほうを推奨します。


Realm 認証

  Shiro の認証処理フローを理解したので、次は、認証試行時に Authenticator と Realm とのやりとりで何が行われているのかを正確に知ることが重要となります。


AuthenticationToken のサポート

  認証シーケンスで触れられているように、認証試行時に Realm へ問い合わせがおこなわれる前に、supports メソッドが呼ばれます。戻り値が true であれば、その場合に限り、getAuthenticationInfo(token) メソッドが呼ばれます。

  典型的には、Realm は、処理できるかどうかを確認するため、渡されたトークンの型 (インターフェースもしくはクラス) を調べます。例えば、生体認証データを処理する Realm は UsernamePasswordToken を全く理解することはないでしょうから、この場合 supports メソッドは false を返します。


サポート対象の AuthenticationToken を処理する

  提示された AuthenticationToken を Realm が処理できるのであれば、Authenticator は Realm の getAuthenticationInfo(token) メソッドを呼びます。これは事実上、Realm の背後にあるデータソースを用いて認証を試みることになります。このメソッドは次の順番で処理をおこないます。

  1. 識別プリンシパル (アカウントを識別する情報) がトークンに含まれているかを調べる。
  2. プリンシパルを元に、データソース内にある対応するアカウントデータを検索する。
  3. トークンが含む認証情報がデータソースに保存されているものと一致することを確認する。
  4. 認証情報が一致する場合は、Shiro が扱える形式でアカウントデータを含めた AuthenticationInfo インスタンスを返す。
  5. 認証情報が一致しない場合は、AuthenticationException を投げる。

  これは、getAuthenticationInfo の全ての実装におけるハイレベル処理フローです。Realm はこのメソッド内で何をやってもよく、例えば、認証試行について監査ログに記録したり、データレコードを更新したり、その他、データソースに対する認証試行に関して意味のあることであれば何でもかまいません。

  唯一求められているのは、与えられたプリンシパルと認証情報が一致するのであれば、null ではない、データソース内の Subject アカウント情報をあらわす AuthenticationInfo インスタンスを返すことです。

時間の節約

  Realm インターフェースを直接実装するのは、時間もかかり、また間違い易くもあります。 ほとんどの人は、ゼロから実装するのではなく、抽象クラス AuthorizingRealm を拡張するほうを選びます。 このクラスには、時間と労力を節約するため、認証と認可の共通処理フローが実装されています。


認証情報の一致検査

  上述の Realm 認証処理フローでは、Realm は提示された Subject の認証情報 (例えばパスワード) がデータソースに保存されている認証情報と一致するかどうかを検証しなければなりません。一致するのであれば、認証成功となり、システムがエンドユーザーの同一性を検証したということになります。

Realm による認証情報一致検査

  提示された認証情報と Realm の背後にあるデータストアに保存されている認証情報とが一致するかどうかを確認するのは、Authenticator ではなく、各 Realm の責任となります。各 Realm は、認証情報のフォーマットとストレージに関する非公開の情報を有し、詳細な認証情報一致検査を実行できますが、一方で Authenticator は汎用的な処理フローコンポーネントに過ぎません。

  認証情報の一致検査処理は、全てのアプリケーションにおいてほぼ同一で、ほとんどの場合、異なるのは比較対象のデータのみです。この処理を必要に応じて取り外したりカスタマイズしたりできるように、AuthenticatingRealm およびそのサブクラスは、認証情報の比較を実行する CredentialsMatcher という概念をサポートしています。

  検索により見つかったアカウントデータと提示された AuthenticationToken は CredentialsMatcher に渡され、提示されたものとデータストアに保存されているものが一致するかどうかが調べられます。

  すぐに利用開始できるよう、SimpleCredentialsMatcherHashedCredentialsMatcher などの CredentialsMatcher の実装が Shiro に用意されています。しかし、カスタム一致検査ロジック用のカスタム実装を用いるように設定したいのであれば、直接おこなうこともできます。

Realm myRealm = new com.company.shiro.realm.MyRealm();

CredentialsMatcher customMatcher =
    new com.company.shiro.realm.CustomCredentialsMatcher();

myRealm.setCredentialsMatcher(customMatcher);

  もしくは、Shiro の INI 設定を用いるのであれば:

[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...


単純な等価検査

  Shiro が用意している Realm の実装は全て、デフォルトでは SimpleCredentialsMatcher を使います。SimpleCredentialsMatcher は、保存されているアカウント認証情報と AuthenticationToken に含まれている提示された認証情報とを、何の変換処理もおこなわずに直接等しいかどうか調べます。

  例えば UsernamePasswordToken が提示されたとすると、SimpleCredentialsMatcher は提示されたパスワードとデータストアに保存されているパスワードが正確に一致するかどうかを検証します。

  とはいえ、SimpleCredentialsMatcher は単純な String 比較以上のことをおこないます。String や文字配列、バイト配列、ファイルや入力ストリーム等の一般的なバイトソースを扱うことができます。詳細は JavaDoc を参照してください。


認証情報のハッシュ化

  認証情報をそのままの形で保存して何の変換処理もおこなわずに比較をおこなう方法に代わる、エンドユーザーの認証情報 (パスワード等) をはるかに安全に保存する方法は、データストアに保存する前に一方向ハッシュを求めてそれを保存することです。

  これにより、エンドユーザーの認証情報がそのままの形では保存されなくなるので、誰も元の値を知ることはできません。これは、平文や単純比較よりもはるかに安全な仕組みであり、セキュリティーを気にするアプリケーションは全て、ハッシュ化機構の無いストレージに対しては、このアプローチを取るべきです。

  この好ましい暗号ハッシュ戦略をサポートするため、先に述べた SimpleCredentialsMatcher の代わりに Realm に設定できる HashedCredentialsMatcher の実装が、Shiro により提供されます。

  認証情報のハッシュ処理、ソルトと複数回ハッシュの利点については、Realm に関する本文書の対象外となりますが、原理について詳細に説明している HashedCredentialsMatcher の JavaDoc を是非とも読んでください。


ハッシュ化と対応する Matcher

  Shiro を用いたアプリケーションでこれを簡単におこなうにはどうしたらよいでしょうか?

  Shiro には HashedCredentialsMatcher のサブクラスの実装が複数あります。あなたがユーザーの認証情報をハッシュ化するときに用いるアルゴリズムに合わせて、特定の実装を選んで Realm に設定しなければなりません。

  例えば、あなたのアプリケーションがユーザー名とパスワードの組により認証をおこなうものとしましょう。そして、先に説明した認証情報ハッシュ化の利点を享受するため、ユーザーアカウントを作る際に SHA-256 アルゴリズムを用いてユーザーのパスワードを一方向ハッシュ処理したいとしましょう。このとき、ユーザーが入力した平文パスワードをハッシュ化し、その値を保存する処理は次のようになります。

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
...

// ソルトを生成するため乱数生成器を用います。これは、ユーザー名を
// ソルトとして用いたり、まったくソルトを用いない場合に比べて
// はるかに安全です。Shiro を使えばとても簡単です。
//
// 普通のアプリでは、毎回乱数生成器を新しく作成するのではなく、
// 属性を参照するだけにとどめます。
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();

// ランダムに生成したソルトを用いて平文パスワードをハッシュ化し、
// それを何回も繰り返してから、Base64 で符号化します (Base64 は
// 十六進数表記よりも必要領域が少なくて済みます)。
String hashedPasswordBase64 =
    new Sha256Hash(plainTextPassword, salt, 1024).toBase64();

User user = new User(username, hashedPasswordBase64);

// ソルトを新しいアカウントと一緒に保存します。ソルトは、ログイン
// 試行時に HashedCredentialsMatcher が必要とします。
user.setPasswordSalt(salt);
userDAO.create(user);

  SHA-256 でユーザーのパスワードをハッシュ化したので、このハッシュ処理に合う HashedCredentilsMatcher を使うように Shiro に伝える必要があります。この例では、セキュリティーを強化するため、ランダムにソルトを生成し、ハッシュ処理を 1024 回繰り返しました (理由は HashedCredentialsMatcher の JavaDoc を参照してください)。Shiro の INI 設定では次のように設定することになります。

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

# この例では十六進数ではなく Base64
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024

# 下記のプロパティーは Shiro 1.0 で必要。1.1 以降では取り除く。
credentialsMatcher.hashSalted = true

...
myRealm = com.company.....
myRealm.credentialsMatcher = $credentialsMatcher
...


SaltedAuthenticationInfo

  この作業の仕上げとしてやらなければならないことは、あなたの Realm の実装で、いつもの AuthenticationInfo ではなく、SaltedAuthenticationInfo のインスタンスを返すことです。SaltedAuthenticationInfo により、あなたがユーザーアカウントを作成したときに使用したソルト (上記の例で言えば user.setPasswordSalt(salt) で保存したソルト) を HashedCredentialsMatcher が参照できるようになります。

  HashedCredentialsMatcher は、データストアに保存してあるトークンと提示された AuthenticationToken が一致することを確認するため、同じハッシュ処理を実行しますが、その際にソルトを必要とします。ですので、ユーザーパスワード用にソルトを用いるのであれば (用いるべきです!!!)、Realm の実装で SaltedAuthenticationInfo インスタンスを返すようにしてください。


認証無効化

  なんらかの理由により (おそらく Realm で認可だけを扱いたいという理由により)、Realm に認証処理をさせたくない場合は、Realm の supports メソッドで常に false を返すことにより Realm の認証サポートを完全に無効化することができます。そうすることで、認証試行の際に Realm に問い合わせがくることはなくなります。

  もちろん、Subject を認証したいのであれば、少なくとも一つの Realm は AuthenticationToken をサポートするよう設定しておく必要があります。


Realm 認可

  記述予定


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

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

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



2013年12月28日土曜日

文字数、UTF-8 でのバイト数、サロゲートペアの数を数える

  下記のコードは、文字数、UTF-8 で符号化したときのバイト数、サロゲートペアの数、を数え上げるロジックを JavaScript で表現したものです。Java や Objective-C など、文字列を UTF-16 で扱っているプログラミング言語であれば、同じロジックを適用できます。GIST はこちら。Unicode 理解度簡易チェックはこちら。本ブログの英語版はこちら



文字数: 0
UTF-8 でのバイト数: 0
サロゲートペアの数: 0

Count up letters, bytes in UTF-8 and surrogate pairs in JavaScript

Below is a sample code in JavaScript to count up letters, bytes in UTF-8 and surrogate pairs. Available as a gist.



0 letter(s).
0 byte(s) in UTF-8.
0 surrogate pair(s).



2013年12月13日金曜日

Unicode 理解度簡易チェック

次のことが理解できているかを簡単に調べることができます。

  • UTF-8 は Unicode の文字符号化方式の一つである
    (Unicode と UTF-8 の違いが分かっているか?)
  • Java や Objective-C でプログラミングするときに文字の値は UTF-16 になっている
    (ちゃんとプログラミングできるか?)

下記の表の内容を見て、全て理解できれば OK です。



Unicode の規格でこの文字に割り当てられた
Unicode スカラ値 (十六進数表記)
U+3042
この文字の Unicode スカラ値を UTF-8
符号化したときのバイト列 (十六進数表記)
E3 81 82
(3 バイト)
この文字の Unicode スカラ値を UTF-16BE
符号化したときのバイト列 (十六進数表記)
30 42
(2 バイト)
この文字の Unicode スカラ値を UTF-16LE
符号化したときのバイト列 (十六進数表記)
42 30
(2 バイト)
この文字の Unicode スカラ値を UTF-32BE
符号化したときのバイト列 (十六進数表記)
00 00 30 42
(4 バイト)
この文字の Unicode スカラ値を UTF-32LE
符号化したときのバイト列 (十六進数表記)
42 30 00 00
(4 バイト)
// Java

char ch = "あ".charAt(0);   // ch == 0x3042
// Objective-C

NSString *s = @"あ";
unichar ch = [s characterAtIndex:0];   // ch == 0x3042
(おまけ) この文字を Shift-JIS
符号化したときのバイト列 (十六進数表記)
82 A0
(2 バイト)




𠮷 (UTF-16 のとき、サロゲートペア)
Unicode の規格でこの文字に割り当てられた
Unicode スカラ値 (十六進数表記)
U+20BB7
この文字の Unicode スカラ値を UTF-8
符号化したときのバイト列 (十六進数表記)
F0 A0 AE B7
(4 バイト)
この文字の Unicode スカラ値を UTF-16BE
符号化したときのバイト列 (十六進数表記)
D8 42 DF B7
(4 バイト)
この文字の Unicode スカラ値を UTF-16LE
符号化したときのバイト列 (十六進数表記)
42 D8 B7 DF
(4 バイト)
この文字の Unicode スカラ値を UTF-32BE
符号化したときのバイト列 (十六進数表記)
00 02 0B B7
(4 バイト)
この文字の Unicode スカラ値を UTF-32LE
符号化したときのバイト列 (十六進数表記)
B7 0B 02 00
(4 バイト)
// Java

String s = "𠮷";
int  len = s.length();    // len == 2
char ch1 = s.charAt(0);   // ch1 == 0xD842
char ch2 = s.charAt(1);   // ch2 == 0xDFB7
// Objective-C

NSString *s = @"𠮷";
int len     = [s length];               // len == 2
unichar ch1 = [s characterAtIndex:0];   // ch1 == 0xD842
unichar ch2 = [s characterAtIndex:1];   // ch2 == 0xDFB7

2013年12月10日火曜日

Apache Shiro で Web アプリケーションをセキュアにする

# Securing Web Applications with Apache Shiro (Nov 19, 2013) の翻訳

  この文書は、Apache Shiro で Web アプリケーションをセキュアにする手順を一つずつ説明していく入門レベルのチュートリアルです。Shiro について初歩的な知識があることを前提としており、少なくとも次の二つの入門文書を理解していることを想定しています。


  このステップ・バイ・ステップのチュートリアルを完了するのには 45 分から 1 時間くらいかかるでしょう。終了後には、Web アプリケーションで Shiro がどのように動作するかを深く理解できていることでしょう。


概要

  コマンドライン・アプリケーションやサーバー・デーモン、Web アプリケーションなど、Java 仮想マシンで動くアプリケーションであれば何であれ、Apache Shiro でセキュアにすることを可能とする、というのが Apache Shiro の核となる設計目標ですが、このガイドでは最も一般的なユースケース、すなわち、TomcatJetty といったサーブレットコンテナ上で動く Web アプリケーションをセキュアにすること、にフォーカスします。


必須要件

  このチュートリアルをこなしていくため、あなたの手元の開発マシンに下記のツール群をインストールしておいてください。


チュートリアルの構成

  これは、ステップ・バイ・ステップのチュートリアルです。チュートリアルそのものと、その全てのステップは、Git リポジトリとして存在しています。その Git リポジトリをクローンすると、master ブランチが開始点となります。チュートリアル内の各ステップは、個別のブランチとなっています。チュートリアル内で参照中のステップを表すGit ブランチをチェックアウトするだけで、チュートリアルについていくことができます。


Web アプリケーション

  これから作ろうとしている Web アプリケーションは、あなた自身の Web アプリケーションの開始点として使用可能な Web アプリケーションです。ユーザーのログイン、ログアウト、ユーザー別のウェルカム・メッセージ、Web アプリケーションに対する部分的なアクセス制御、置き換え可能なセキュリティーデータ保存領域との統合、を実例で説明していきます。

  プロジェクトのセットアップ、ビルドツールの導入、依存関係の宣言、Web アプリケーションと Shiro 環境を起動するための web.xml の設定、から始めます。

  セットアップ後、セキュリティーデータ保存領域との統合、ユーザーログインとログアウト、アクセス制御などの個々の機能を一つずつ加えていきます。


プロジェクトのセットアップ

  ディレクトリ構造と基本的なファイル群の初期セットを用意する作業は、我々が既に Git リポジトリでやっておきました。


1. チュートリアルプロジェクトをフォークする

  GitHubチュートリアルプロジェクトにいき、右上にある Fork ボタンを押してください。


2. チュートリアルリポジトリをクローンする

  あなたの GitHub アカウントでリポジトリをフォークしたので、次は手元のマシンにクローンしてください。

$ git clone git@github.com:$YOUR_GITHUB_USERNAME/\
apache-shiro-tutorial-webapp.git

(もちろん、$YOUR_GITHUB_USERNAME はあなたの GitHub ユーザー名でおきかえてください)

  これでクローンしたディレクトリに移動し、プロジェクト構成を見ることができます。

$ cd apache-shiro-tutorial-webapp


3. プロジェクト構成を確認する

  リポジトリをクローンしたあと、master ブランチは次のような構成となっています。

apache-shiro-tutorial-webapp/
  |-- src/
  |  |-- main/
  |    |-- resources/
  |      |-- logback.xml
  |    |-- webapp/
  |      |-- WEB-INF/
  |        |-- web.xml
  |      |-- home.jsp
  |      |-- include.jsp
  |      |-- index.jsp
  |-- .gitignore
  |-- .travis.yml
  |-- LICENSE
  |-- README.md
  |-- pom.xml

  それぞれ次のような意味です。

  • pom.xml: Maven プロジェクトのビルドファイル。Jetty の設定は済ませてあるので、mvn jetty:run ですぐに Web アプリケーションをテストできます。
  • README.md: プロジェクトの README ファイル
  • LICENSE: プロジェクトのライセンス。Apache 2.0 ライセンス
  • .travis.yml: プロジェクトを常にビルド可能状態に保つことを保証するために継続的インテグレーションを実行したくなったときのための Travis CI 設定ファイル。
  • .gitignore: Git 無視設定ファイル。バージョン管理下に置かないファイルのサフィックスやディレクトリをリストします。
  • src/main/resources/logback.xml: Logback 設定ファイル。このチュートリアルで我々は、ロギング API として SLF4J を、実装として Logback を選びました。Log4J や JUL (java.util.logging) にすることも容易にできたでしょう。
  • src/main/webapp/WEB-INF/web.xml: 我々の最初の web.xml ファイルです。すぐあとで Shiro を有効にするよう設定します。
  • src/main/webapp/include.jsp: 共通の import と宣言を含むページで、他の JSP から読み込まれます。これにより import と宣言を一ヶ所で管理できます。
  • src/main/webapp/home.jsp: 我々の Web アプリケーションのデフォルトホームページです。include.jsp を読み込みます (すぐあとで見ることになりますが、他のファイルも include.jsp を読み込みます。)
  • src/main/webapp/index.jsp: デフォルトのサイト index ページです。単に home.jsp ホームページへとリクエストを転送するだけです。


4. Web アプリケーションを実行する

  プロジェクトをクローンしてあるので、コマンドラインで次のように実行すれば Web アプリケーションを起動できます。

$ mvn jetty:run


  続けて Web ブラウザで localhost:8080 を開けば、ホームページに Hello, World! と表示されることが確認できます。

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。


ステップ 1 : Shiro を有効にする

  初期状態の master ブランチは、どんなアプリケーションのテンプレートにもなりうる汎用 Web アプリケーションに過ぎません。次は、Shiro を有効にするための最低限の作業をしていきましょう。

  step1 ブランチをロードするため、次の git checkout コマンドを実行してください。

$ git checkout step1

  このブランチをチェックアウトすると、変更が二つかかります。

  1. 新しく src/main/webapp/WEB-INF/shiro.ini ファイルが追加され、
  2. src/main/webapp/WEB-INF/web.xml が変更されます。


1a : shiro.ini ファイルを追加する

  Web アプリケーション内では、あなたが使用している Web / MVC フレームワークに応じて、様々な方法で Shiro を設定することができます。例えば、Spring, Guice, Tapestry やその他数多くの方法で Shiro を設定することができます。

  ここでは話を簡単に済ますため、Shiro のデフォルトの (そしてとてもシンプルな) INI ベースの設定を使って Shiro 環境を始めてみましょう。

  step1 ブランチをチェックアウトしてあれば、新しくできた src/main/webapp/WEB-INF/shiro.ini ファイルの中身を確認することができます (分かりやすくするため、ヘッダーコメントは取り除いてあります):

[main]

# 実行中の Stormpath への問い合わせの数を減らすため、メモリでの
# キャッシュを使うことにします。実際のアプリケーションでは、
# もっとしっかりしたキャッシュ機構 (Ehcache や分散キャッシュ等) が
# 望ましいでしょう。そのようなキャッシュを使うときは、キャッシュの
# TTL 設定に気を付けてください。TTL が大き過ぎると、Stormpath 側で
# 発生したかもしれない変化がキャッシュに反映されるまでに時間がかなり
# かかってしまいます。一方で、小さ過ぎると、キャッシュが無効になる
# 頻度がかなり高くなってしまいます。
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager

  この .ini ファイルでは [main] セクションで最低限の設定をしています。

  • 新しく cacheManager インスタンスを定義しています。Shiro のアーキテクチャーではキャッシュは重要です。多種多様なデータ保存領域とのやりとりを減らすものだからです。 この例では、単一 JVM で動くアプリケーションにとってだけはかなり良い MemoryConstrainedCacheManager を使っています。複数のホスト (例えばクラスター化された Web サーバーファーム等) にまたがって配備されるアプリケーションの場合は、クラスター化された CacheManager の実装をかわりに使いたくなることでしょう。
  • Shiro の securityManager に新しい cacheManager を設定しています。Shiro の SecurityManager インスタンスは常に存在するので、わざわざ定義する必要はありません。


1b : web.xml で Shiro を有効にする

  設定ファイル shiro.ini は用意できましたが、これを実際にロードして新規に Shiro 環境を開始し、Web アプリケーションで使えるようにする必要があります。

  既に存在する src/main/webapp/WEB-INF/web.xml ファイルに幾つか項目を追加するだけで、全て完了します:

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

  • <listener> 宣言では、Web アプリケーション起動時に Shiro 環境 (Shiro の SecurityManager を含む) を開始する ServletContextListener を定義しています。このリスナーは、Shiro の設定を探すときにデフォルトで WEB-INF/shiro.ini を見にいく、という動作を自動的におこないます。
  • <filter> 宣言では、マスター ShiroFilter を定義しています。Web アプリケーションにリクエストを渡す前に必要な識別処理やアクセス制御処理を Shiro がおこなえるよう、このフィルターで Web アプリケーションに届く全てのリクエストにフィルターをかけることを想定しています。
  • <filter-mapping> 宣言では、全てのリクエストタイプを ShiroFilter で処理するようにしています。ほとんどの場合 filter-mapping 宣言で <dispatcher> 要素を指定することはありませんが、Shiro では、Web アプリケーションが実行しうる全てのリクエストタイプにフィルターをかけられるよう、全て定義する必要があります。


1c : Web アプリケーションを実行する

  step1 ブランチをチェックアウト後、Web アプリケーションを実行してください:

$ mvn jetty:run

  今回は、下記と似た感じのログ出力を見ることになります。これは、Web アプリケーション内で Shiro が実際に実行されていることを示すものです。

16:04:19.807 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Starting Shiro environment initialization.
16:04:19.904 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Shiro environment initialized in 95 ms.

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。



ステップ 2 : ユーザー格納領域に接続する


  step2 ブランチをロードするため、次の git checkout コマンドを実行してください。

$ git checkout step2

  Shiro を統合し、Web アプリケーション内で走らせました。しかし、我々はまだ Shiro に何も指示を出していません!

  ログインやログアウト、ロールやパーミッションに基づくアクセス制御、その他セキュリティーに関係することをやる前に、まずはユーザーが必要です!

  ログイン試行時のユーザー検索や、ロールに基づくセキュリティー判断などのためには、何らかのユーザー格納領域にアクセスするように Shiro を設定する必要があります。アプリケーションがアクセスすることになるユーザー格納領域は多種多様です。MySQL データベースにユーザーを格納するかもしれませんし、MongoDB かもしれません。あなたの会社ではユーザーアカウントを LDAP や Active Directory で管理しているかもしれませんし、ただのファイルやその他のプロプライエタリーなデータストアかもしれません。

  Shiro ではこれを、いわゆるレルム (Realm) という形で扱います。Shiro のドキュメンテーションからの引用です:

  レルムは、Shiro とアプリケーションのセキュリティーデータとの間のブリッジ/コネクタとして振る舞います。認証 (ログイン) や認可 (アクセス制御) のためにユーザーアカウントのようなセキュリティーデータと実際にやりとりを行うとき、Shiro はアプリケーション用に設定された一つ以上のレルムに対して多くのことを問い合わせます。

  この意味において、レルムは本質的にセキュリティーに特化した DAO であると言えます。データソースとの接続詳細をカプセル化し、必要なときに関連データを Shiro に提供するものです。Shiro を設定するとき、認証/認可で使用するレルムを一つ以上指定しなければなりません。SecurityManager に複数のレルムを設定してもよいですが、少なくとも一つは必要です。

  Shiro は、LDAP やリレーショナルデータベース (JDBC)、INI や properties ファイル等のテキストベースの設定など、多数のセキュリティーデータソース (またの名をディレクトリ) に接続するためのレルムをすぐ使える状態で提供しています。もし要件に合うレルムがなければ、カスタムのデータソースを表現するレルムの実装をプラグインすることもできます。

  というわけで、ユーザーを扱うためにレルムを設定する必要があります。


2a : Stormpath をセットアップする

  チュートリアルをできるだけシンプルにしようと思っていますので、複雑な話を持ち込んだり範囲を広げたりして Shiro を学ぶという目的から外れるようなことは避けたいと思います。かわりに最もシンプルなレルムの一つである Stormpath レルムを使用することにします。

  Stormpath はクラウド上のユーザー管理サービスで、開発用途であれば完全に無料です。つまり、Stormpath を有効にすると、下記のものがすぐに使えるようになります。

  • アプリケーション、ディレクトリー、アカウント、グループを管理するためのユーザーインターフェース。これらは Shiro には全く含まれていないので、このチュートリアルをこなしていく上では便利であり、時間を節約できます。
  • ユーザーのパスワードをストレージ内でセキュアに保つ仕組み。あなたのアプリケーションでは、パスワードのセキュリティー、比較、保存について気にする必要は全くありません。Shiro でこれらを扱うこともできますが、設定をしなければならず、暗号の概念についても理解していなければなりません。Stormpath はパスワードセキュリティーを自動化しているので、あなた (と Shiro) は「正しいやり方」について心配したり、問題を抱えたりする必要はありません。
  • メールによるアカウント検証やパスワードリセットなどのセキュリティー・ワークフロー。これは通常アプリケーション固有の事項なので、Shiro ではサポートしていません。
  • ホストされ、監視されている「常時稼働」インフラ。維持管理のために何かを設定する必要はありません。

  チュートリアルの目的からすると、別途 RDBMS サーバーをセットアップしたり、SQL やパスワード符号化問題を心配したりするよりも、Stormpath を使うほうがはるかに簡単です。ですので、ここでは Stormpath を使います。

  もちろん、Stormpath は、Shiro がやりとりできる数あるバックエンド・データストアの一つでしかありません。もっと複雑なデータストアやアプリケーション固有の設定については、あとで説明します。


Stormpath にサインアップする

  1. Stormpath の登録フォームを埋め、送信します。これにより確認メールが届きます。
  2. 確認メールに含まれるリンクをクリックします。


Stormpath API キーを取得する

  Stormpath とやりとりする Stormpath レルムには、Stormpath API キーが必要です。Stormpath API キーを取得する手順は次のとおりです。

  1. Stormpath に登録したメールアドレスとパスワードを使って Stormpath 管理コンソールにログインします。
  2. 表示されたページの右上角から、SettingsMy Account と進みます。
  3. アカウント詳細ページで、Security Credentials の下にある Create API Key をクリックします。これにより、あなた用の API キーが作成され、apiKey.properties ファイルが手元のコンピューターにダウンロードされます。そのファイルをテキストエディタで開くと、次のような内容を確認できます。
    apiKey.id = 144JVZINOF5EBNCMG9EXAMPLE
    apiKey.secret = lWxOiKqKPNwJmSldbiSkEbkNjgh2uRSNAb+AEXAMPLE
    
  4. このファイルを、ホームディレクトリ下の隠しディレクトリ .stormpath などといった安全な場所に保存します。例:
    $HOME/.stormpath/apiKey.properties
    
  5. また、このファイルを自分だけしか見れないようにするため、ファイルのパーミッションを変更してください。例えば、Unix 系オペレーティングシステムであれば:
    $ chmod go-rwx $HOME/.stormpath/apiKey.properties
    


Web アプリケーションを Stormpath に登録する

  アプリケーションから Stormpath を使いユーザーの管理と認証をおこなうためには、Web アプリケーションを Stormpath に登録しなければなりません。アプリケーションの登録は Stormpath に REST リクエストを投げるだけでできます。下記は、新しいアプリケーションのリソースを Stormpath の applications URL に POST する方法です。

curl -X POST --user $YOUR_API_KEY_ID:$YOUR_API_KEY_SECRET \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
           "name" : "Apache Shiro Tutorial Webapp"
        }' \
    'https://api.stormpath.com/v1/applications?createDirectory=true'

  ここで、

  • $YOUR_API_KEY_IDapiKey.properties 内の apiKey.id の値で、
  • $YOUR_API_KEY_SECRETapiKey.properties 内の apiKey.secret の値です。

  これによりアプリケーションが作成されます。レスポンスの例は次のようになります。

{
    "href": "https://api.stormpath.com/v1/applications/aLoNGrAnDoMAppIdHeRe",
    "name": "Apache Shiro Tutorial Webapp",
    "description": null,
    "status": "ENABLED",
    "tenant": {
        "href": "https://api.stormpath.com/v1/tenants/sOmELoNgRaNDoMIdHeRe"
    },
    "accounts": {
        "href": "https://api.stormpath.com/v1/applications/aLoNGrAnDoMAppIdHeRe/accounts"
    },
    "groups": {
        "href": "https://api.stormpath.com/v1/applications/aLoNGrAnDoMAppIdHeRe/groups"
    },
    "loginAttempts": {
        "href": "https://api.stormpath.com/v1/applications/aLoNGrAnDoMAppIdHeR/loginAttempts"
    },
    "passwordResetTokens": {
        "href": "https://api.stormpath.com/v1/applications/aLoNGrAnDoMAppIdHeRe/passwordResetTokens"
    } 
}

  トップレベルの href である https://api.stormpath.com/v1/applications/$YOUR_APPLICATION_ID を書き留めておいてください。この href は、shiro.ini の設定で使います。


アプリケーションテスト用ユーザーアカウントを作成する

  アプリケーションができたので、サンプルテストユーザーを作成しようと思います:

curl -X POST --user $YOUR_API_KEY_ID:$YOUR_API_KEY_SECRET \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d '{
           "givenName": "Jean-Luc",
           "surname": "Picard",
           "username": "jlpicard",
           "email": "capt@enterprise.com",
           "password":"Changeme1"
        }' \
 "https://api.stormpath.com/v1/applications/$YOUR_APPLICATION_ID/accounts"

  上記の URL の $YOUR_APPLICATION_ID をあなたのアプリケーション ID に変更することを忘れないでください。


2b : shiro.ini でレルムを設定する

  Shiro の要請にあわせて、少なくとも一つの接続先ユーザー格納領域を選び、それからそのデータ領域を表現する Realm を設定し、Shiro の SecurityManager に伝える必要があります。

  step2 ブランチをチェックアウトすると、次の内容が shiro.ini ファイルの [main] セクションに追加されます。

# ユーザーデータ格納領域に接続する Realm を設定します。このシンプルな
# チュートリアルでは、Stormpath を指すだけです。セットアップ時間は 5 分。
stormpathClient = com.stormpath.shiro.client.ClientFactory
stormpathClient.cacheManager = $cacheManager
stormpathClient.apiKeyFileLocation = $HOME/.stormpath/apiKey.properties
stormpathRealm = com.stormpath.shiro.realm.ApplicationRealm
stormpathRealm.client = $stormpathClient

# Stormpath コンソールで、あなたが作成するアプリケーション用の URL を
# 見つけてください (Applications → アプリケーション名選択 → Details
# → REST URL)。
stormpathRealm.applicationRestUrl = https://api.stormpath.com/v1/applications/$STORMPATH_APPLICATION_ID
stormpathRealm.groupRoleResolver.modeNames = name
securityManager.realm = $stormpathRealm

  次の変更をおこなってください。

  1. 最終的に stormpathClient.apiKeyFileLocation の値が /home/jsmith/.stormpath/apiKey.properties のようなものになるよう、$HOME を実際のホームディレクトリパス (例:/home/jsmith) に変更してください。このパスは、ステップ 2a でダウンロードした apiKey.properties ファイルの場所と同じでなければなりません。
  2. $STORMPATH_APPLICATION_ID を、ステップ 2a の最後で Stormpath が href として返してきた、実在する ID に変更してください。最終的に stormpathRealm.applicationRestUrl の値は次のような感じになります: https://api.stormpath.com/v1/applications/6hsPwoRZ0hCk6ToytVxi4D (もちろんアプリケーション ID の部分は異なります)


2c : 変更をコミットする

  変更した $HOME$STORMPATH_APPLICATION_ID  の値はあなたのアプリケーションに固有のものです。これらの変更をあなたのブランチにコミットしてください。

$ git add . && git commit -m "アプリ固有の値に置き換えた。"


2d : Web アプリケーションを実行する

  ステップ 2b と 2c で説明した変更を加えたあと、Web アプリケーションを起動してください。

$ mvn jetty:run

  今回は、次のようなログ出力が得られます。あなたの Web アプリケーションで、Shiro と新しいレルムが適切に設定されていることを示しています。

16:08:25.466 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Starting Shiro environment initialization.
16:08:26.201 [main] INFO  o.a.s.c.IniSecurityManagerFactory - Realms have been explicitly set on the SecurityManager instance - auto-setting of realms will not occur.
16:08:26.201 [main] INFO  o.a.shiro.web.env.EnvironmentLoader - Shiro environment initialized in 731 ms.

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。


ステップ 3 : ログイン・ログアウトを有効にする

  さて、我々にはユーザーがいます。そして、UI を使って簡単にユーザーの追加、削除、無効化ができます。アプリケーションへのログイン・ログアウトやアクセス制御といった機能を有効にする作業を始めることができます。

  step3 ブランチをロードするため、次の git checkout コマンドを実行してください。

$ git checkout step3

  このチェックアウトにより、次の二つの変更がかかります。

  • 簡単なログインフォームを持つ src/main/webapp/login.jsp ファイルが新しく追加されます。これでログインをおこないます。
  • Web (URL) 固有の機能をサポートするために shiro.ini ファイルが更新されます。


3a : Shiro のフォームログイン・ログアウトのサポートを有効にする

  step3 ブランチの src/main/webapp/WEB-INF/shiro.ini ファイルには次の二つが追加されます。

[main]
shiro.loginUrl = /login.jsp

# [main] セクションでこれまでに設定した内容は、分かりやすくするため省略。

[urls]
/login.jsp = authc
/logout = logout


shiro.*

  [main] セクションの先頭に、新しい行があります。

shiro.loginUrl = /login.jsp

  これは特別な設定ディレクティブで、Shiro に対して、「Shiro のデフォルトフィルター群のうち loginUrl プロパティーを持つもの全てについて、そのプロパティー値を /login.jsp に設定せよ」、と指示するものです。

  これにより、Shiro のデフォルト authc フィルター (デフォルトでは FormAuthenticationFilter) がログインページについて知ることができます。FormAuthenticationFilter が正しく動作するためには、これが必要なのです。


[urls] セクション

  [urls] セクションは、新しい Web 固有の INI セクションです。

  このセクションでは、非常に簡潔な {名前 - 値}・シンタックスを用いて、指定の URL パスに対するリクエストをどのようにフィルターするかを Shiro に指示します。[urls] セクション内の全てのパスは、Web アプリケーションの HttpServletRequest.getContextPath() の値からの相対パスです。

  この {名前 - 値} の組は、リクエストをフィルターする極めて強力な方法を提供し、あらゆる種類のセキュリティールールを扱えます。URL とフィルターのチェインに関する深い説明はこの文書の範囲外ですが、興味があれば、こちらをお読みください

  ここでは、追加された 2 行を説明しましょう。

/login.jsp = authc
/logout = logout

  • 一番目の行は、「/login.jsp という URL へのリクエストを受けたら常に、そのリクエストの間、Shiro の authc フィルタを有効にする」、ということを示しています。
  • 二番目の行は、「/logout という URL へのリクエストを受けたら常に、そのリクエストの間、Shiro の logout フィルターを有効にする」、ということを意味しています。

  これらのフィルターは両方とも少し特殊です。両方とも、実際には、何かが後ろに控えていることを要求しないのです。フィルターするかわりに、これらのフィルターはリクエストを全て処理してしまいます。つまり、これらの URL に対するリクエストを処理するためにあなたがやらなければならないことは何もないのです (コントローラーを書かなくてもよいのです!)。Shiro が必要に応じてリクエストを処理します。


3b : ログインページを追加する

  ステップ 3a でログイン・ログアウトのサポートを有効にしたので、次は、ログインフォームを表示する /login.jsp ページを実際に用意する必要があります。

  step3 ブランチには、新しく src/main/webapp/login.jsp ページが含まれています。Bootstrap をテーマに用いた HTML で、十分にシンプルなログインページとなっていますが、そこには 4 つの重要なポイントがあります。

  1. フォームの action は空文字列です。フォームに action が設定されていないとき、ブラウザはフォームリクエストを同じ URL  に submit します。これで良いのです。なぜなら、自動的にログイン submit を処理するよう、対象 URL を Shiro に伝えるからです。shiro.ini 内の /login.jsp = authc という行が、authc フィルターでログイン submit を処理することを指示している部分です。
  2. username というフォームフィールドがあります。Shiro の authc フィルターはログイン submit を処理するとき、自動的に username というリクエストパラメーターを探し、その値をログインに使用します (多くのレルムが username でメールアドレスやユーザー名を受け取ります)。
  3. pasword というフォームフィールドがあります。Shiro の authc フィルターはログイン submit を処理するとき、自動的に password というリクエストパラメーターを探します。
  4. rememberMe チェックボックスがあります。checked 状態の値は、真だと認識できそうな値 (true, t, 1, enabled, y, yes, on) にすることができます。

  我々の login.jsp フォームでは、デフォルトの username, password, rememberMe フォールフィールド名を使用しています。これらの名前は設定で変更することも可能です。詳細は FormAuthenticationFilter の JavaDoc を参照してください。


3c : Web アプリケーションを実行する

  ステップ 3a と 3b で説明した変更を加えたあと、Web アプリケーションを起動してください。

$ mvn jetty:run


3d : ログインする

  Web ブラウザで localhost:8080/login.jsp を開くと、そこには我々の輝かしいログインフォームがあります。

  ステップ 2 の最後で作成したアカウントのユーザー名とパスワードを入力し、Login を押してください。ログインに成功すれば、ホームページに遷移します! ログインに失敗した場合はログインページが再度表示されます。

  Tip: ログイン成功時にホームページ (= コンテキストパス / ) 以外の場所にユーザーをリダイレクトさせたいときは、authc.successUrl = /whatever を INI ファイルの [main] セクションで設定してください。

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。


ステップ 4 : ユーザーごとに UI を変更する

  ユーザーが誰であるかに応じて Web ユーザーインターフェースを変更するという要望はよくあることです。Shiro は、現在ログイン中の Subject (ユーザー) をベースに処理をおこなうための JSP タグライブラリをサポートしているので、こういったことは簡単におこなえます。

  step4 ブランチをロードするため、次の git checkout コマンドを実行してください。

$ git checkout step4

  これにより、home.jsp ページに次の変更が加えられます。

  • 現在ページを閲覧中のユーザーがログインしていない場合、「Welcome Guest」のメッセージとログインページへのリンクが表示されます。
  • 現在ページを閲覧中のユーザーがログインしている場合、「Welcome ユーザー名」というメッセージとログアウトするリンクが表示されます。

  画面右上にユーザーコントロールを表示するナビゲーションバーでは、この手の UI カスタマイズはとても一般的なことです。


4a : Shiro タグライブラリの宣言を追加する

  home.jsp への変更で、先頭に次の 2 行が追加されます。

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

  この二つの JSP ページディレクティブで、Core タグライブラリ (c:) と Shiro タグライブラリ (shiro:) を使用できるようになります。


4b : Shiro のゲストタグとユーザータグを追加する

  home.jsp はページのボディー部分 (<h1> ウェルカムメッセージの直後) も変更され、<shiro:guest><shiro:user> タグの両方が追加されます。

<p>Hi <shiro:guest>Guest</shiro:guest><shiro:user>
<%
  // 通常、これはページ内でやるべきではなく、かわりに、何らかの
  // 適切な MVC コントローラー内でやるべきです。しかし、この
  // チュートリアルでは、下記の <c:out/> タグで参照するため、
  // Shiro の PrincipalCollection から Stormpath アカウントの
  // データを取り出しています。
  request.setAttribute("account",
    org.apache.shiro.SecurityUtils.getSubject().getPrincipals()
    .oneByType(java.util.Map.class));
%>
<c:out value="${account.givenName}"/></shiro:user>!
  ( <shiro:user>
      <a href="<c:url value="/logout"/>">Log out</a>
    </shiro:user>
    <shiro:guest>
      <a href="<c:url value="/login.jsp"/>">Log in</a>
    </shiro:guest> )
</p>

  形を整えるため少し読みづらくなっていますが、二つのタグは次の箇所で使われています。

  • <shiro:guest>: このタグは、現在の Shiro Subject がアプリケーションのゲストである場合のみ、内部コンテンツを表示します。Shiro は、アプリケーションにログインしていない、もしくは (Shiro の remember me 機能を利用して) 前回のログインを記憶していない Subject をゲストと定義しています。
  • <shiro:user>: このタグは、現在の Shiro Subject がアプリケーションのユーザーである場合のみ、内部コンテンツを表示します。Shiro は、現在アプリケーションにログインしている (認証されている)、もしくは (Shiro の remember me 機能を利用して) 前回のログインを記憶している Subject をユーザーと定義しています。

  上記のコードは、Subject がゲストであれば次の表示をおこないます。

Hi Guest! (Log in)

  ここで、Log in は /login.jsp へのリンクになります。

  Subject がユーザーの場合は次のように表示します。

Hi jsmith! (Log out)

  ここではログインしているアカウントのユーザー名が jsmith であると仮定しています。Log out は、Shiro の logout フィルターで処理される /logout という URL へのリンクになります。

  ここまで見てきたとおり、ページセクション全体や、機能、UI コンポーネントといったものを有効化・無効化することができます。<shiro:guest><shiro:user> だけではなく、現在の Subject に関する様々な情報をもとに UI をカスタマイズするための数多くの便利な JSP タグを、Shiro はサポートしています。


4c : Web アプリケーションを実行する

  step4 ブランチをチェックアウト後、Web アプリケーションを起動してください。

$ mvn jetty:run

  localhost:8080 にゲストとしてアクセスし、それからログインしてください。ログインが成功すると、あなたが既知のユーザーであることを反映してページの内容が更新されます!

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。


ステップ 5 : 認証済みユーザーのみアクセスを許可する

  Subject の状態に応じてページの内容を変更することは可能ですが、一方で、ユーザーが認証済みかどうかに応じて、現在のインタラクション中は Web アプリケーションのセクション全体に制限をかけたいと思うこともあるでしょう。

  これは、Web アプリケーションのユーザーのみが見ることを許された慎重に扱うべき情報、例えば支払詳細や他のユーザーを制御する機能、などを表示するときは特に重要です。

  step5 ブランチをロードするため、次の git checkout コマンドを実行してください。

$ git checkout step5

  ステップ 5 では次の 3 つが変更されています。

  1. 認証済みユーザーのみにアクセスを許す新しいセクション (URL パス) が追加されています。
  2. shiro.ini が変更され、認証済みユーザーのみ当該セクションにアクセスが許されるようになっています。
  3. 現在の Subject が認証済みであるかどうかに応じて出力を変えるよう、ホームページが変更されています。

5a : 制限付きの新しいセクションを追加する

  src/main/webapp/account ディレクトリが新しく追加されています。このディレクトリ (および下位の全てのパス) は「プライベート」もしくは「認証済みに限る」セクションを表していて、ログインしているユーザーのみに制限するセクションをシミュレートしています。src/main/webapp/account/index.jsp ファイルは、シミュレートされる「ホームアカウント」ページです。


5b : shiro.ini を設定する

  shiro.ini も変更され、[urls] セクションの末尾に次の行が追加されています。

/account/** = authc

  ここで Shiro フィルターチェインを定義していますが、意味は、「/account (およびその下位パス) へのリクエストは全て認証されなければならない」、となります。

  しかしながら、もし誰かがこのパスもしくはその下位パスにアクセスした場合、何が起こるのでしょうか?

  ステップ 3 で [main] セクションに次の行を追加したことを覚えているでしょうか?

shiro.loginUrl = /login.jsp

  この行により、authc フィルターに Web アプリケーションのログイン URL が自動的に設定されたのでしたね。

  この設定行により、authc フィルターは賢くなっていて、現在の Subject が /account にアクセスしたときに認証済みでなければ、Subject を /login.jsp ページに自動的にリダイレクトします。ログイン成功後は、ユーザーがアクセスしようとしていたページ (/account) に自動的にリダイレクトされます。便利ですね!


5c : ホームページを更新する

  ステップ 5 の最後の変更は、Web サイトに新しくアクセスできるようになった場所があることをユーザーに知らせるよう、 /home.jsp ページを更新することです。ウェルカムメッセージのあとに次の行が追加されています。

<shiro:authenticated>
  <p>
    Visit your <a href="<c:url value="/account"/>">account page</a>.
  </p>
</shiro:authenticated>
<shiro:notAuthenticated>
  <p>
    If you want to access the authenticated-only
    <a href="<c:url value="/account"/>">account page</a>,
    you will need to log-in first.
  </p>
</shiro:notAuthenticated>

  <shiro:authenticated> タグは、現在のセッションで Subject が既にログイン済み (認証済み) の場合のみ、内容を表示します。これにより Subject は、Web サイトに新しくアクセスできるようになった場所があることを知ります。

  <shiro:notAuthenticated> タグは、現在のセッションで Subject がまだ認証されていない場合のみ、内容を表示します。

  ところで、notAuthenticated の内容に /account セクションの URL が含まれていることには気付かれたでしょうか? これは問題ありません。authc フィルターは、ログイン→リダイレクト・フローを先に説明したように扱います。

  新しい変更とともに Web アプリケーションを起動し、試してみてください!



5d : Web アプリケーションを実行する

  step5 ブランチをチェックアウト後、Web アプリケーションを起動してください。

$ mvn jetty:run

  localhost:8080 にアクセスして新しくできた /account リンクをクリックし、ログインページにリダイレクトされることを確認してください。ログイン後、ホームページに戻り、内容が変化してあなたが認証されていることを確認してください。ログアウトするまでは、アカウントページとホームページの行き来を何回でも望むだけできます。素晴らしいでしょう!

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。


ステップ 6 : ロールに基づくアクセス制御

  認証に基づくアクセス制御に加え、現在の Subject に与えられたロールに応じてアプリケーションの特定部部へのアクセスを制限するという要望も、よくあるものです。

  step6 ブランチをロードするため、次の git checkout コマンドを実行してください。

$ git checkout step6


6a : ロールを追加する

  ロールに応じたアクセス制御をおこなうには、ロールが存在しなければなりません。

  このチュートリアルでそれをおこなう最速の方法は、Stormpath にグループを作ることです (Stormpath では、Stormpath Group をロールと同様の目的で使えます)。

  これをおこなうには、UI にログインし次のように進み、

DirectoriesApache Shiro Tutorial Webapp DirectoryGroups

下記の 3 つのグループを追加してください。

  • Captains
  • Officers
  • Enlisted

(スター・トレックのアカウントテーマを保つためです :) )

  グループ作成後、Jean-Luc Picard アカウントを Captains グループと Officers グループに追加してください。その場限りのアカウントを作成して好きなグループに追加してもよいでしょう。ユーザーアカウントに割り当てられたグループの違いに応じた変化を確認できるよう、幾つかのアカウントはグループがかぶらないようにしてください。


6b : RBAC (Role-Based Access Control) タグ

  どのロールを持っていてどのロールを持っていないのかをユーザーが確認できるよう、/home.jsp ページを更新します。ホームページに <h2>ロール</h2> セクションを新しく作成し、次のメッセージ群を加えてください。

<h2>ロール</h2>

<p>
あなたが持っているロールと持っていないロールです。
ログアウトして別のユーザーアカウントでログインし直すと、
ロールが変化します。
</p>

<h3>あなたが持っているロール:</h3>

<p>
    <shiro:hasRole name="Captains">Captains<br/></shiro:hasRole>
    <shiro:hasRole name="Officers">Bad Guys<br/></shiro:hasRole>
    <shiro:hasRole name="Enlisted">Enlisted<br/></shiro:hasRole>
</p>

<h3>あなたが持っていないロール:</h3>

<p>
    <shiro:lacksRole name="Captains">Captains<br/></shiro:lacksRole>
    <shiro:lacksRole name="Officers">Officers<br/></shiro:lacksRole>
    <shiro:lacksRole name="Enlisted">Enlisted<br/></shiro:lacksRole>
</p>

  <shiro:hasRole> タグは、指定されたロールが現在の Subject に割り当てられている場合のみ、内容を表示します。

  <shiro:lacksRole> タグは、指定されたロールが現在の Subject に割り当てられていない場合のみ、内容を表示します。


6c : RBAC フィルターチェイン

  読者の皆さんに残された練習 (ステップとしては定義されていません) は、Web サイトに新しいセクションを作成し、現在のユーザーに割り当てられたロールに基づいてそのセクションへの URL アクセスを制限することです。

  ヒント: ロール・フィルターを用いて、Web アプリケーションの新しい箇所へのフィルターチェインを宣言します。



6d : Web アプリケーションを実行する

  step6 ブランチをチェックアウト後、Web アプリケーションを起動してください。

$ mvn jetty:run

  localhost:8080 を開き、異なるロールを割り当てられた別々のユーザーアカウントでログインし、ホームページのロール・セクションの内容が変化することを確認してください!

  Ctrl-C (Mac であれば Cmd-C) を入力して Web アプリケーションを終了させてください。


まとめ

  Web アプリケーションで Shiro を活用する方法を説明したこの入門チュートリアルが、あなたの役に立てたのであれば幸いです。このチュートリアルの続編では次のトピックをカバーする予定です。

  • Shiro の極めて強力なパーミッションとパーミッションに基づくアクセス制御
  • RDBMS や NoSQL データストアなどの、異なるユーザーデータ格納領域の組み込み


修正と Pull リクエスト

  何か間違いがあれば修正を GitHub の Pull リクエストとして https://github.com/lhazlewood/apache-shiro-tutorial-webapp リポジトリに送ってください。ご協力に感謝します!!!