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 リポジトリに送ってください。ご協力に感謝します!!!