本格派エンジニアの工具箱

第21回Apache Shiroを利用して、スタンドアロンプログラムでセッションを有効にする

Apache Shiroのセッション管理機能

前回に引き続き、Javaアプリケーション向けの認証フレームワークApache Shiroの使い方を紹介します。Shiroには、認証機能に関連していくつかのうれしい付加機能が備わっています。その1つがセッション管理機能です。セッションを使えば、ユーザに紐付いた情報などを一定期間アプリケーション側に保持しておくことができるようになります。

Webアプリケーションであればjavax.servlet.http.HttpSessionなどによってセッションを扱うことができますが、Shiroでは、スタンドアロンのアプリケーションにおいても認証機構と連動したセッションを使うことができるようになっています。

Shiroでセッションを扱うにはorg.apache.shiro.session.Sessionインターフェース(とその実装クラス)を利用します。使い方はHttpSessionとよく似ています。まず、Sessionオブジェクトは次ようにSubjectのgetSession()メソッドを用いて取得します。

// セッションの取得(作成)
Subject user = SecurityUtils.getSubject();
Session session = user.getSession(true);

このメソッドは、すでにセッションが確立している場合にはそのSessionoオブジェクトを返します。まだセッションがない場合には、引数がfalseのときはnullを、trueのときは新たにSessionオブジェクトを生成して返します。

セッションへのキーと値の設定はaddAttribute()メソッドを使います。このメソッドにはキーとなるオブジェクトと、それに関連付ける値のオブジェクトをセットで渡します。値を取り出すには、getAttribute()メソッドにキーを渡せば、そのキーに関連付けられた値のオブジェクトが返されます。

// セッションにキーと値をセット
session.setAttribute("key", "value");

// セッションから値の取得
String attribute = session.getAttribute("key");

次のコードは、Shiroによる認証とセッション管理を利用したプログラムの例です。ログイン後にコンソールから入力されたテキストをセッションに記録します。SubjectクラスのisAuthenticated()メソッドは、そのSubjectが認証済みである場合にtrueを返すというものです。つまりこのプログラムは、初回はログイン処理を行ってセッションを作成し、以後はセッションからの値の読み出しと新しい値の設定を繰り返し行うようになっているわけです。そして"LOGOUT"が入力された場合に、ログアウト処理を行ってプログラムを終了します。

public class SessionSample {
  public static void main(String[] args) {
    // セキュリティマネージャの設定
    Factory factory = 
            new IniSecurityManagerFactory("classpath:resources/manager.ini");
    SecurityUtils.setSecurityManager(factory.getInstance());
    
    // Subjectの取得
    Subject user = SecurityUtils.getSubject();

    while(true) {
      Session session = null;
      if (!user.isAuthenticated()) {
        try {
          // Tokenの作成
          UsernamePasswordToken token = 
                  new UsernamePasswordToken("gihyo", "gihyopass");
        
          // ログイン
          user.login(token);
          System.out.println("----ログインしました。");
        
          // セッションの取得(作成)
          session = user.getSession(true);
          
          // コンソールからの入力値をセッションのプロパティにセット
          System.out.println("----任意のテキストを入力してください。");
          String input = System.console().readLine();
          session.setAttribute("input", input);
        } catch (AuthenticationException ex) {
          System.out.println("Authentication failed.\n" + ex);
        }
      } else {
        System.out.println("----ログイン中です。");
      
        if ((session = user.getSession(false)) != null) {
          System.out.println("前回の入力テキスト: " + session.getAttribute("input"));
          // コンソールからの入力をセッショプロパティにセット
          // ただし"LOGOUT"だった場合はログアウト処理
          System.out.println("----任意のテキストを入力してください。");
          String input = System.console().readLine();
          if (input.equals("LOGOUT")) {
            user.logout();
            System.out.println("----ログアウトしました。");
            break;
          } else {
            session.setAttribute("input", input);
          }
        }
      }
    }
  }
}

このプログラムの実行例は以下のようになります。Sessionオブジェクトを経由して情報が記録できていることが確認できます。

----ログインしました。
----任意のテキストを入力してください。
Hello!                 

Shiroにおけるセッションは、セッションストレージと呼ばれるDAO(Data Access Object)形式のデータ永続化の仕組みを利用して情報を記憶しています。デフォルトではメモリ上に永続化するシンプルな実装が使われますが、その他にキャッシュ機構を利用して永続化する仕組みのセッションストレージを利用することができます。そして、商用レベルのアプリケーションではそのような実装を使うことが推奨されます。

とくにEHCacheを利用した実装についてはshiro-ehcacheパッケージ(shiro-allパッケージにも含まれます)として公式にサポートされているので、比較的簡単に利用することができます。詳細は公式のリファレンスを参照してください。

セッションの開始/終了時にリスナを起動する

Shiroのセッションでは、開始時と終了時のイベントを受け取るリスナを登録することもできます。リスナはorg.apache.shiro.session.SessionListenerインターフェースをimplementsして作ります。以下に例を示します。

public class MySessionListener extends SessionListenerAdapter {
  public void onStart(Session session)  {
    System.out.println("----Session started.");
  }
  
  public void onStop(Session session) {
    System.out.println("----Session stopped.");
  } 
}

SessionListenerAdapterクラスはSessionListenerの空の実装です。onStart()はセッションが開始されたときに呼び出されるメソッドです。onStop()はセッションが停止されたとき、具体的にはSessionクラスのstop()メソッドが呼ばれたときに呼び出されます。stop()メソッドはSubjectのlogout()メソッドの実行時にも自動的に呼ばれるので、ログアウトした際にもこリスナが反応するということです。

リスナの登録は、iniファイルに設定を記述することで行います。上の例ではmanager.iniにからSecurityManagerの設定を読み込んでいるので、このmanager.iniの[main]セクションにおいて、securityManager.sessionManager.sessionListenersプロパティに次の例のように登録したいリスナのクラス名を設定します。なお、リスナは複数登録することもできます。

[main]
......
sessionListener = jp.gihyo.toolbox.shiro.MySessionListener
securityManager.sessionManager.sessionListeners = $sessionListener
......

この状態でSessionSampleを実行すると、以下のように、セッションの開始時と終了時にそれぞれリスナのメソッドが実行されることが確認できます。

----Session started.
----ログインしました。
----任意のテキストを入力してください。
Hello!                 

なお、使用するSessionManagerの実装によっては、設定ファイルではなくプログラム内でSessionListenerを登録することもできます。デフォルトで使用されるSessionManager実装のDefaultSessionManagerクラスもそのひとつです。このクラスに用意されているsetSessionListeners()メソッドを使い、次のようにSessionListenerを格納したCollectionオブジェクトを渡すことで登録を行います。

// SessionListenerの登録
DefaultSecurityManager securityManager = 
    (DefaultSecurityManager)SecurityUtils.getSecurityManager();
DefaultSessionManager sessionManager = 
    (DefaultSessionManager)securityManager.getSessionManager();
List sessionListeners = new ArrayList();
sessionListeners.add(new MySessionListener());
sessionManager.setSessionListeners(sessionListeners);

おすすめ記事

記事・ニュース一覧