Apache Shiro の紹介

Web アプリケーションのユーザー認証に Apache Shiro を使う

Shiro は Apache Incubator プロジェクトの 1 つであり、認証と承認を容易にすることを目的としています。この記事では Apache Shiro について紹介するとともに、Groovy Web アプリケーションで Shiro を使用して認証と承認を行う方法を試すためのサンプル・アプリケーションについて説明します。

Nathan A. Good, Senior Information Engineer, Freelance Developer

Nathan GoodNathan A. Good はミネソタ州の Twin Cities エリアに住んでいます。彼はプロとしてソフトウェア開発やソフトウェア・アーキテクチャー、システム管理などを行っています。彼はソフトウェアを書いている時以外は、PC やサーバーを構築したり、新しい技術について資料を読んだり、そうした技術に取り組んだり、彼の友人達をオープソース・ソフトウェアに移行させようとしたりしています。彼は数多くの本や記事を執筆、あるいは共同で執筆しており、その中には『Professional Red Hat Enterprise Linux 3』や『Regular Expression Recipes: A Problem-Solution Approach』、『Foundations of PEAR: Rapid PHP Development』などがあります。



2010年 9月 14日

Apache Shiro は認証と承認のためのフレームワークです。この記事では、いくつかの例をとおして Java™ アプリケーションで Shiro を使う方法を説明するとともに、Grails Web アプリケーションの中で Shiro を使う方法の概要を説明します。この記事を最大限に活用するためには、Java アプリケーションを難なく作成できること、また以下のコンポーネントをインストールしておくことが必要になります。

  • Java 1.6 JDK
  • Grails (Web アプリケーションのサンプルを実行するため)

よく使われる頭文字語

  • API: Application Programming Interface
  • HTTP: HyperText Transfer Protocol
  • JAR: Java ARchive
  • JDBC: Java DataBase Connectivity
  • JDK: Java software Development Kit
  • LDAP: Lightweight Directory Access Protocol

認証と承認

システムをセキュアにする場合、セキュリティー要素として重要なものは認証と承認の 2 つです。この 2 つの用語の意味は異なりますが、どちらもアプリケーションのセキュリティーに関するものであるため、一方が他方の意味に使われる場合があります。

認証では、ユーザーの身元を検証します。ユーザーを認証する場合には、そのユーザーが実際に主張どおりのユーザーなのかどうかを確認します。ほとんどのアプリケーションでは、認証はユーザー名とパスワードの組み合わせで行われます。ユーザーの選択したパスワードが他の人にとって非常に推測しにくいものである限り、通常はユーザー名とパスワードの組み合わせのみで本人であることを十分に証明することができます。ただし、指紋、証明書、生成されたキーなどの手段を利用した認証を行うこともできます。

認証プロセスによって本人であることが証明されると、今度は承認によってアクセスの制限または許可を行います。認証によってユーザーがシステムにログインできたものの、承認によって、何の操作も許可されない、ということがあり得ます。また、認証されていないユーザーに対して一定レベルの承認を与えることも可能です。

アプリケーションのセキュリティー・モデルを検討する場合には、両方の要素を考慮し、十分なレベルのセキュリティーが施されたシステムとなるようにする必要があります。多くのアプリケーションで (ユーザー名とパスワードのみで認証する場合は特に)、認証がよく問題になります。そのため、フレームワークに認証処理を行わせるという考え方は適切です。適切なフレームワークには、テストと保守が行われているというメリットがあるため、開発者はビジネスの問題に集中することができ、既に解決されている問題を再度解決する必要がありません。

Apache Shiro は使いやすいセキュリティー・フレームワークであり、さまざまなクライアントのアプリケーションに適用することができます。この記事では例をとおして Shiro を紹介し、ユーザーを認証するための基本的なタスクに焦点を絞ります。


Shiro について理解する

Shiro は Java 言語で実装されたフレームワークであり、認証と承認の両方の機能が、使いやすい API として提供されています。Shiro を使用することでアプリケーションのセキュリティーを実現することができ、すべてのコードを最初から作成する必要がありません。

IBM のトータル認証ソリューション

ホワイトペーパー「Enhancing Identity Assurance across all Access Scenarios Cost Effectively with IBM ISS Identity and Access Management Services」は、多種多様なビジネス・アプリケーションやアクセス・シナリオに対する認証ソリューションとして、コスト効果が高く、完全統合されたエンタープライズ・ワイドの認証ソリューションである IBM ISS Identity and Access Management Services について解説しています。

このソリューションにより、非常にセキュアで一元化された認証基盤を実現することができます。この認証基盤は、API や標準的な認証プロトコル (RADIUS や LDAP) を介して、さまざまなアプリケーションや IT 基盤コンポーネントと容易に統合することができます。また、このソリューションではハードウェア、ソフトウェア、あるいは携帯電話ベースのさまざまな認証トークンをサポートしているため、非常に柔軟に特定の認証方法を選択することができます。

Shiro は非常に多様なデータ・ソースによって、また Enterprise Session Management によって認証を行えるため、シングル・サインオン (SSO) の実装に最適です (1 日のあいだに、ユーザーが頻繁にログインしてさまざまなシステムを利用する大企業にとって、シングル・サインオンは望ましい機能です)。そうしたデータ・ソースには、JDBC、LDAP、Kerberos、Microsoft® Active Directory® Directory Services (AD DS) などがあります。

Shiro の Session オブジェクトを使うと、HttpSession を使わなくてもユーザーのセッションを使用することができます。さらに、汎用の Session オブジェクトを使用することで、たとえ Web アプリケーションの中では実行されていないコードでも使用することができます。アプリケーション・サーバーや Web アプリケーション・サーバーのセッション管理を使う必要がないため、コマンドライン環境でも Shiro を使うことができます。つまり、Shiro の API を使って作成したコードでコマンドライン・アプリケーションを作成し、そのアプリケーションから LDAP サーバーに接続することができます。また Shiro の API を使って作成したコードは、LDAP サーバーにアクセスする Web アプリケーションの中のコードと同じものになります。

Shiro のダウンロードとインストール

Shiro にはビルド済みのバイナリー・ディストリビューションが用意されています。Shiro の JAR ファイルをダウンロードすることも、Apache Maven や Apache Ivy に Shiro のエントリーを配置してファイルを自動的にインストールすることもできます。この例では、Ivy と単純なスクリプト (リスト 1) を使って Shiro の JAR ファイルや他の必要なライブラリーをダウンロードしています。

リスト 1. Apache Ivy ファイルと Apache Ant スクリプト
<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd">
    <info organisation="com.nathanagood.examples" module="shirotest" />
    <configurations>
        <conf name="dist" description="Dependency configuration for distribution." />
    </configurations>
    <dependencies>
        <dependency org="commons-logging" name="commons-logging"
            rev="1.1.1" conf="dist->default" />
        <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.5.8"
            conf="dist->default" />
        <dependency org="org.apache.shiro" name="shiro-core" rev="1.0.0-incubating"
            conf="dist->default" />
        <dependency org="org.apache.shiro" name="shiro-web" rev="1.0.0-incubating"
            conf="dist->default" />
    </dependencies>
</ivy-module>

<project name="shiroTestApp" default="usage" basedir="." 
    xmlns:ivy="antlib:org.apache.ivy.ant">
    <property name="project.lib" value="lib" />
    <path id="ivy.task.path">
        <fileset dir="${basedir}/ivy-lib">
            <include name="**/*.jar" />
        </fileset>
    </path>

    <target name="resolve">
        <taskdef resource="org/apache/ivy/ant/antlib.xml" 
            uri="antlib:org.apache.ivy.ant" classpathref="ivy.task.path" />
        <ivy:resolve />
        <ivy:retrieve pattern="${project.lib}/[conf]/[artifact].[ext]" sync="true" />
    </target>

    <target name="usage">
        <echo message="Use --projecthelp to learn more about this project" />
    </target>
</project>

Ivy の使い方の詳細は「参考文献」を参照してください。Maven や Ivy を使用していない読者の方は、ダウンロード・サイトから Shiro の JAR ファイルをダウンロードする必要があります。ダウンロード・サイトについても「参考文献」を参照してください。

必要なライブラリーをダウンロードしたら、それらのライブラリーを単純に CLASSPATH に追加します。次に、簡単なコードを作成します (リスト 2)。このコードはカレント・ユーザーへの参照を取得し、そのユーザーが認証されていないことを伝えるメッセージをログに出力します (Subject クラスを使ってユーザーを表現します)。

リスト 2. ShiroTest Java クラス
package com.nathanagood.examples.shirotest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ShiroTest {

    private static Logger logger = LoggerFactory.getLogger(ShiroTest.class);

    public static void main(String[] args) {
        // Using the IniSecurityManagerFactory, which will use the an INI file
        // as the security file.
        Factory<org.apache.shiro.mgt.SecurityManager> factory = 
            new IniSecurityManagerFactory("auth.ini");

        // Setting up the SecurityManager...
        org.apache.shiro.mgt.SecurityManager securityManager 
            = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        Subject user = SecurityUtils.getSubject();

        logger.info("User is authenticated:  " + user.isAuthenticated());
    }
}

このコードを追加したら、auth.ini という名前のファイルを作成します。さしあたり、このファイルは空白ですが、auth.ini がないとコードを実行できず、コードの動作が適切かどうかを確認することができないため、auth.ini を用意する必要があります。

auth.ini ファイルを作成したら、このサンプルを実行します。すると、そのユーザーはログインしていないことを伝える INFO レベルのロギング・メッセージが出力されるはずです。

SecurityUtils オブジェクトはシングルトンです。これはつまり、さまざまなオブジェクトに SecurityUtils オブジェクトを使うことでカレント・ユーザーにアクセスできるということです。SecurityManager が適切に設定されると、アプリケーションのさまざまな部分で SecurityUtils.getSubject() を呼び出し、カレント・ユーザーの情報を取得することができます。


ユーザー・トークン

Shiro の用語では、トークンはシステムにログインするためのキーのことです。基本的で一般的なトークンは UsernamePasswordToken です。UsernamePasswordToken を使用すると、ユーザーの名前とパスワードを指定することができます。

UsernamePasswordToken クラスは AuthenticationToken インターフェースを実装しており、このインターフェースによってクレデンシャルとユーザーのプリンシパル (アカウントの ID) を取得することができます。UsernamePasswordToken は大部分のアプリケーションで使用することができますが、必要に応じて、AuthenticationToken インターフェースを継承したクラスに、クレデンシャルを取得するための独自のメソッドを含めることができます。例えば AuthenticationToken インターフェースを継承し、ユーザー認証用にアプリケーションが使用する、鍵ファイルの内容を提供することができます。


単純な認証

ここまで、この単純な例を使用して、Shiro の SecurityManager の起動、カレント・ユーザーの取得、そしてユーザー認証がされていないことをログに記録する方法について説明しました。今度の例では、UsernamePasswordToken と、INI ファイルに保存されたユーザー・レコードを使用して、ユーザー名とパスワードで認証を行う方法を説明します。

リスト 3 に示すサンプルの auth.ini ファイルには、今度はユーザーのレコードが含まれています。このレコードには、ユーザー名とパスワードの両方が含まれています。このレコードの中でロールを定義し、アプリケーションのための承認を行うこともできます。

リスト 3. auth.ini ファイル
[users]
bjangles = dance

ここで、この前のセクションで紹介した UsernamePasswordToken オブジェクトを作成します (リスト 4)。

リスト 4. UsernamePasswordToken クラスを使う
// snipped... same as before.
public class ShiroTest {

    private static Logger logger = LoggerFactory.getLogger(ShiroTest.class);

    public static void main(String[] args) {
        // Using the IniSecurityManagerFactory, which will use the an INI file
        // as the security file.
        Factory<org.apache.shiro.mgt.SecurityManager> factory = 
            new IniSecurityManagerFactory("auth.ini");

        // Setting up the SecurityManager...
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        Subject user = SecurityUtils.getSubject();

        logger.info("User is authenticated:  " + user.isAuthenticated());
        
        UsernamePasswordToken token = new UsernamePasswordToken("bjangles", "dance");
        
        user.login(token);
        
        logger.info("User is authenticated:  " + user.isAuthenticated());
    }
}

UsernamePasswordToken オブジェクトはユーザー名とパスワードの組み合わせによってインスタンス化されます。次に Subject クラスの login() メソッドにトークンが渡されます。

このサンプルを再度実行します。今度は、ユーザーが認証済みであることを伝えるロギング・メッセージが出力されることに注意してください。

このコードが適切に動作していること、また誤検出をしているわけではないことを確認するために、コードの中、または INI の中でパスワードを変更し、再度サンプルを実行します。今度は login() メソッドによって IncorrectCredentialsException がスローされます。本番コードでは、この例外を確実にキャッチし、不正なパスワードをユーザーが入力した場合にアプリケーションが適切に対応できるようにします。

ユーザーが不正であった場合には、login() メソッドによって UnknownAccountException がスローされます。この例外の処理方法を検討する必要がありますが、ユーザーに情報を与えすぎないように注意する必要があります。よく使われるプラクティスとしては、ユーザー名は有効でもパスワードが不正である、ということをユーザーに知らせないようにします。誰かが推測でアクセスしようとした場合、その人に対し、推測で入力したユーザー名が正しいというヒントを与えてはなりません。


LDAP による認証

LDAP は TCP/IP 上でディレクトリーに対して問い合わせをするためのプロトコルです。これらのディレクトリーには、いくらでもユーザーの情報を格納することができます (ユーザー ID、連絡先情報、グループへの所属情報、など)。LDAP ディレクトリーは企業用の住所録として便利であり、また広く使用されています。

ユーザーやグループの管理用として一般的に使われているディレクトリーである AD DS は、LDAP をサポートしています。Shiro には汎用の LDAP セキュリティー・レルムは含まれていませんが、ActiveDirectoryRealm オブジェクトが含まれており、このオブジェクトを使って LDAP でユーザーを認証することができます。この例では、INI ファイルの中で構成された ActiveDirectoryRealm オブジェクトを使用してユーザーを認証します。AD DS は LDAP と同じものではありませんが、この記事で使用しているバージョンの Shiro には汎用の LDAP オブジェクトが用意されていません。

このサンプル・アプリケーションのテスト用に LDAP サーバーを設定するためには、このサンプル・アプリケーション自体を作成して実行するよりも、はるかに多くの作業が必要です。AD DS サーバーを利用できない場合には、Apache Directory をダウンロードしてインストールし、LDAP サーバーのサンプル実装を使用することを検討してみてください。Apache Directory は Java 言語で作成されています。同様に、Apache Active Directory Studio は LDAP データをブラウズできる Eclipse プラグインです。Apache Active Directory Studio にはサンプル・データも用意されており、これらのサンプル・データを使うことで、既知の値に対して簡単にコードを作成することができ、遭遇する問題がコードの問題なのかデータの問題なのか迷うことがなくなります。

リスト 5 に、Apache Directory に格納されているユーザーを認証するためのコードを示します。

リスト 5. LDAP で認証する
// snipped...
public class ShiroLDAPTest {

    private static Logger logger = LoggerFactory.getLogger(ShiroLDAPTest.class);

    /**
     * @param args
     */
    public static void main(String[] args) {

        // Using the IniSecurityManagerFactory, which will use the an INI file
        // as the security file.
        Factory<org.apache.shiro.mgt.SecurityManager> factory = 
            new IniSecurityManagerFactory("actived.ini");

        // Setting up the SecurityManager...
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        Subject user = SecurityUtils.getSubject();

        logger.info("User is authenticated:  " + user.isAuthenticated());

        UsernamePasswordToken token = 
        new UsernamePasswordToken(
            "cn=Cornelius Buckley,ou=people,o=sevenSeas", "argh");

        user.login(token);

        logger.info("User is authenticated:  " + user.isAuthenticated());
    }
}

INI ファイルの名前、ユーザー名とパスワードを別にすると、このコードは、INI ファイルの中にあるレコードを使ってユーザーを認証する場合に使われたコードと同じです。このように似ている理由は、INI ファイルによって Shiro を構成することができるためです。Apache Directory で認証できるように Shiro を設定するための INI のレコードをリスト 6 に示します。

リスト 6. actived.ini ファイル
[main]
activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
activeDirectoryRealm.systemUsername = uid=admin,ou=system
activeDirectoryRealm.systemPassword = secret
activeDirectoryRealm.searchBase = o=sevenSeas,ou=people
activeDirectoryRealm.url = ldap://localhost:10389

注: ここでは Apache Directory Studio を使ってユーザーのパスワードを変更しました。そして変更した値をテスト・コードに入れ、そのパスワードが実際に使えることを確認しました。


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

Shiro は Web アプリケーションの基本的な 2 つの手法に適用することができます。1 つは単純に API を使用することで、ここで説明したコードをサーブレットに組み込むことができます。もう 1 つは、Shiro に用意された HTTP フィルターを使用することができます。この例では2 番目の手法を説明します。HTTP フィルターを使用すると、Shiro プロジェクトに組み込まれている Web アプリケーション・サーバー技術と作成済みのコードを利用できるからです。

このサンプル・アプリケーションでは、Grails でフィルターを使用する方法を説明します。Grails プロジェクトの目的は、「設定よりも規約」の手法を使って Groovy Web アプリケーションをできるだけ短時間で作成できるようにすることです。Grails について詳しくは「参考文献」を参照してください。

通常は、Shiro のフィルターに必要なフィルター・エントリーを手動で web.xml ファイルに追加します。しかし Grails はアプリケーションが起動されるたびに web.xml ファイルが生成されるため、手動で web.xml ファイルを変更する必要はありません。

幸いなことに、Grails でサポートされているプラグインを利用すると、web.xml ファイル生成プロセスにフックし、web.xml ファイルにエントリーを書き込むことができます。既に多くのプラグインが Grails 用に用意されており、その中には Shiro 用のプラグインもあるので、Shiro Grails プラグインを試してみてください。Shiro Grails プラグインには新しいスクリプトがいくつか用意されており、それらのスクリプトを実行することで、さまざまなレルムやコントローラーを作成することができます。

あるいは、エントリーを追加して独自に構成をしたい場合には、独自のプラグインを作成することもできます。Grails では、新しいプラグインの作成は容易です。Shiro フィルター用に必要なエントリーを web.xml ファイルに追加する Grails プラグインを作成するためには、最初に以下のコマンドを使用します。

> grails create-plugin ShiroWebXml

プラグイン・プロジェクトを作成したら、ShiroWebXmlPlugin.groovy ファイルを編集し、リスト 7 のコードを追加します。

リスト 7. プラグインの例
class ShiroWebXmlPlugin {
   
   // snipped plugin details... 

   def doWithWebDescriptor = { xml ->

       def filterElement = xml.'filter'
       def lastFilter = filterElement[filterElement.size() - 1]

       lastFilter + {
           'filter' {
               'filter-name'("ShiroFilter")
               'filter-class'("org.apache.shiro.web.servlet.IniShiroFilter")
               'init-param' {
                   'param-name'("config")
                   'param-value'("\n#config")
               }
           }
       }

       def filterMappingElement = xml.'filter-mapping'
       def lastFilterMappingElement = 
           filterMappingElement[filterMappingElement.size() - 1]

       lastFilterMappingElement + {
           'filter-mapping' {
               'filter-name'("ShiroFilter")
               'url-pattern'("/*")
               }
           }
       }
}

このコードは、Grails が web.xml ファイルを実行すると、実行されます。

プラグイン・アプリケーション起動してテストする前に、Ivy を使ってダウンロードした Shiro の JAR ファイルをプラグインの lib フォルダーにコピーします。JAR ファイルが用意できたら、このプラグインが動作するかどうか、以下のコマンドを使ってテストします。

grails run-app

プラグイン・アプリケーションが無事に起動したら、サンプル・プロジェクトで使えるようにパッケージ化します。プラグインをパッケージ化するためには、以下のコマンドを使います。

grails package-plugin

新しい ShiroWebXmlPlugin をインストールするためには、以下のコマンドを使います。

cd myapp
grails install-plugin /path/to/shiro-web-xml-1.0.zip

トラブルシューティング

UnavailableSecurityManagerException が発生した場合には、SecurityManager が適切に設定されていないということなので、SecurityUtils オブジェクトに SecurityManager が設定されていることを確認してから getSubject() メソッドを呼び出します。

LDAP サーバーへの接続は簡単ではありません。javax.naming.CommunicationException が発生した場合には、LDAP サーバーのホスト名とポートを確認します。Apache Directory を使用していない場合でも、(別途インストールできる) Apache Directory Studio は、接続の問題と名前の問題を解決する上で役に立ちます。

環境の初期化に Ant Ivy スクリプトを使用しなかった場合には、クラスが見つからない、というエラーが発生する可能性があります。INI ファイルのサンプルは Apache Commons の Logging ライブラリー (commons-logging) がなくても実行できますが、LDAP のサンプルを実行するとエラーが発生します。Apace Commons の BeanUtils を使って INI ファイルを構文解析し、オブジェクトの値を設定します。


まとめ

Shiro は Apache Incubator のフレームワークの 1 つであり、Shiro によってアプリケーションに認証機能と承認機能を追加することができます。Shiro は、LDAP、Kerberos、AD DS など、さまざまな認証ストアをサポートしています。Shiro は依存関係をほとんど持たず、構成も比較的容易なため、アプリケーションのセキュリティー・フレームワークの優れた選択肢の 1 つです。

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=551205
ArticleTitle=Apache Shiro の紹介
publish-date=09142010