第2章 JAAS の概略

JBossSX フレームワークは JAAS API を基にしています。JBossSX の実装について詳しく理解するためには、JAAS API の基本要素を理解する必要があります。JBossSX アーキテクチャについては本書の後半で説明するため、次項では JAAS について少し説明しておきます。
JAAS 1.0 API はユーザー認証と承認を目的としてつくられた Java パッケージのセットで構成されています。API は標準の PAM (Pluggable Authentication Modules : プラグ可能な認証モジュール) フレームワークの Java バージョンを実装し、ユーザーベースの承認に対応するよう Java 2 Platform のアクセス制御アーキテクチャを拡張します。
JAAS は最初 JDK 1.3 の拡張パッケージとしてリリースされ、JDK 1.5 に同梱されています。JBossSX フレームワークは JAAS の認証機能のみを使用して、宣言型ロールベースの J2EE セキュリティモデルを実装するため、本項ではこの部分にのみ焦点を置いて説明します。
JAAS 認証はプラグ可能な方法で実行されます。これにより Java アプリケーションが基礎となる認証技術に依存することなく、JBossSX セキュリティマネージャが異なるセキュリティインフラストラクチャで動作することが可能になります。セキュリティインフラストラクチャとの統合は JBossSX セキュリティマネージャの実装を変更することなく実現できます。変更が必要なのは、JAAS が使用する認証スタックの設定のみです。

2.1. JAAS コアクラス

JAAS コアクラスは共通、認証、承認の 3 つのカテゴリに区分することができます。本項で取り上げる JBossSX の機能性を実装するために使用されるクラスは共通と認証のクラスであるため、以下の一覧ではそれら 2 つのみ記載します。
共通のクラスは次の通りです。
  • Subject (javax.security.auth.Subject)
  • Principal (java.security.Principal)
認証のクラスは次の通りです。
  • Callback (javax.security.auth.callback.Callback)
  • CallbackHandler (javax.security.auth.callback.CallbackHandler)
  • Configuration (javax.security.auth.login.Configuration)
  • LoginContext (javax.security.auth.login.LoginContext)
  • LoginModule (javax.security.auth.spi.LoginModule)

2.1.1. サブジェクトとプリンシパルクラス

リソースへのアクセスを承認するには、アプリケーションは最初に要求元を認証する必要があります。JAAS フレームワークは要求元を表すための用語サブジェクトを定義します。Subject のクラスは JAAS の中心クラスです。Subject は人やサービスなど単一のエンティティの情報を表します。情報はエンティティのプリンシパル、パブリックの資格情報、プライベートの資格情報などに渡ります。JAAS API は既存の Java 2 java.security.Principal インターフェースを使用して、プリンシパルを表します。プリンシパルは基本的に入力された名前になります。
認証プロセスでは、サブジェクトには関連付けられたアイデンティティまたはプリンシパルが含まれています。サブジェクトは多くのプリンシパルを持つことができます。例えば、一個人は名前のプリンシパル (John Doe)、ソーシャルセキュリティ番号のプリンシパル (123-45-6789)、ユーザー名のプリンシパル (johnd) を持つことができ、これらすべては他のサブジェクトから区別するのに役立ちます。1 つのサブジェクトに関連付けられたプリンシパルを取得する方法は 2 つあります。
public Set getPrincipals() {...}
public Set getPrincipals(Class c) {...}
getPrincipals() はサブジェクトに含まれるすべてのプリンシパルを返します。getPrincipals(Class c) はクラス c のインスタンスまたはそのサブクラスのひとつであるプリンシパルのみを返します。サブジェクトに一致するプリンシパルがない場合は空のセットが返されます。
java.security.acl.Group インターフェースは java.security.Principal のサブインターフェースであるため、プリンシパルのセットのインスタンスは他のプリンシパルやプリンシパルのグループの論理グループを表します。

2.1.2. サブジェクトの認証

サブジェクトの認証には JAAS ログインが必要です。ログイン手順は次のようになります。
  1. アプリケーションは LoginContext のインスタンスを作成し、ログイン設定の名前と CallbackHandler を渡して、設定 LoginModule で必要とされるとおり Callback オブジェクトを追加します。
  2. LoginContext は、名前付きログイン設定に含まれているすべての LoginModules をロードするよう Configuration と確認します。そうした名前付き設定が存在しない場合は、other 設定がデフォルトとして使用されます。
  3. アプリケーションが LoginContext.login メソッドを呼び出します。
  4. ログインメソッドはロードされたすべての LoginModule を呼び出します。各 LoginModule はサブジェクトの認証を試行するため、関連付けられた CallbackHandler でハンドルメソッドを呼び出し、認証プロセスに必要な情報を取得します。必要な情報は Callback オブジェクトのアレイの形式でハンドルメソッドに渡されます。成功すると、LoginModule は関連のプリンシパルと資格情報をサブジェクトに関連付けします。
  5. LoginContext はアプリケーションに認証状態を返します。ログインメソッドからの返されると成功となります。ログインメソッドによって LoginException がスローされると失敗となります。
  6. 認証が成功したら、アプリケーションは LoginContext.getSubject メソッドを使用して認証されたサブジェクトを取得します。
  7. サブジェクトの認証が完了した後に LoginContext.logout メソッドを呼び出すと、ログインメソッドによりサブジェクトに関連付けられたすべてのプリンシパルと関連情報を削除することができます。
LoginContext クラスは認証しているサブジェクトに基本メソッドを提供し、基礎となる認証技術に依存しないアプリケーションを開発する方法を提供します。LoginContext は特定のアプリケーション向けに設定された認証サービスを決定するよう Configuration と確認します。LoginModule クラスは認証サービスを表します。そのため、アプリケーション自体を変更することなくログインモジュールをアプリケーションにプラグインすることが可能です。次のコードは、サブジェクトを認証するためアプリケーションに必要となるステップを示しています。
CallbackHandler handler = new MyHandler();
LoginContext lc = new LoginContext("some-config", handler);

try {
    lc.login();
    Subject subject = lc.getSubject();
} catch(LoginException e) {
    System.out.println("authentication failed");
    e.printStackTrace();
}
                        
// Perform work as authenticated Subject
// ...

// Scope of work complete, logout to remove authentication info
try {
    lc.logout();
} catch(LoginException e) {
    System.out.println("logout failed");
    e.printStackTrace();
}
                        
// A sample MyHandler class
class MyHandler 
    implements CallbackHandler
{
    public void handle(Callback[] callbacks) throws
        IOException, UnsupportedCallbackException
    {
        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof NameCallback) {
                NameCallback nc = (NameCallback)callbacks[i];
                nc.setName(username);
            } else if (callbacks[i] instanceof PasswordCallback) {
                PasswordCallback pc = (PasswordCallback)callbacks[i];
                pc.setPassword(password);
            } else {
                throw new UnsupportedCallbackException(callbacks[i],
                                                       "Unrecognized Callback");
            }
        }
    }
}
開発者は LoginModule インターフェースの実装を作成することで、認証技術を統合します。これにより管理者は異なる認証技術を 1 つのアプリケーションにプラグインできます。複数の LoginModule をチェーン化し複数の認証技術を認証プロセスに加えることが可能です。例えば、ある LoginModule がユーザー名 / パスワードベースの認証を行い、別の LoginModule はスマートカードリーダや生体認証などのハードウェアデバイスに接続することができます。
LoginModule のライフサイクルは、クライアントがログインメソッドを作成し公開するLoginContext オブジェクトによって決定されます。このプロセスには 2 つのフェーズがあり、プロセスの手順は次のようになります。
  • LoginContext は引数のないパブリックコンストラクタを使用して、設定された LoginModule を作成します。
  • LoginModule は initialize メソッドへの呼び出しによって初期化されます。Subject 引数は null 以外になることが保証されます。initialize メソッドのシグネチャは public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) です。
  • login メソッドは認証プロセスを開始するために呼び出されます。例えば、あるメソッド実装はユーザーにユーザー名とパスワードの入力を求め、NIS または LDAP などのネーミングサービスで保存されているデータに対してこの情報を確認することがあります。別の実装ではスマートカードや生体認証デバイスに接続されるか、単に基礎となるオペレーティングシステムからユーザー情報を抽出することがあります。各 LoginModule によるユーザーアイデンティティの検証は JAAS 認証のフェーズ 1 とみなされます。login メソッドのシグネチャは boolean login() throws LoginException です。LoginException は失敗を意味します。true の戻り値はメソッドが成功したことを示し、false の戻り値はログインモジュールが無視されることを示しています。
  • LoginContext の全体的な認証が成功すると、各 LoginModulecommit が呼び出されます。フェーズ 1 が LoginModule に対し成功すると、コミットメソッドではフェーズ 2 が続き、関連するプリンシパル、パブリックの資格情報、プライベートの資格情報をサブジェクトに関連付けます。フェーズ 1 が LoginModule に対し失敗すると、commit はユーザー名やパスワードなど以前に保存していた認証状態をすべて削除します。commit メソッドのシグネチャは boolean commit() throws LoginException です。LoginException がスローされると、コミットフェーズの完了が失敗したことを示します。true が返されるとメソッドが成功したことを示し、false が返されるとログインモジュールが無視されることを示します。
  • LoginContext の全体的な認証が失敗すると、各 LoginModuleabort メソッドが呼び出されます。abort メソッドはログインまたは initialize メソッドによって作成されたすべての認証状態を削除または破棄します。abort メソッドのシグネチャは boolean abort() throws LoginException です。LoginException がスローされると abort フェーズの完了が失敗したことを示します。true が返されるとメソッドが成功したことを示し、false が返されるとログインモジュールが無視されることを示します。
  • ログイン成功後に認証状態を削除するには、アプリケーションは LoginContextlogout を呼び出します。これにより、各 LoginModulelogout メソッド呼び出しが発生します。logout メソッドは commit 動作時に当初サブジェクトに関連付けられていたプリンシパルと資格情報を削除します。資格情報は削除時に破棄されるべきです。logout メソッドのシグネチャは boolean logout() throws LoginException です。LoginException がスローされるとログアウトプロセスの完了が失敗したことを示します。true が返されるとメソッドが成功したことを示し、false が返されるとログインモジュールが無視されることを示します。
LoginModule が認証情報を取得するためユーザーと交信する必要がある場合、CallbackHandler オブジェクトを使用します。アプリケーションは CallbackHandler インターフェースを実装してそれを LoginContext に渡し、直接基礎となるログインモジュールに認証情報を送ります。
ログインモジュールは、パスワードやスマートカード PIN などのユーザーからの入力を収集し、状態情報などをユーザーに提供するために CallbackHandler を使用します。アプリケーションに CallbackHandler を指定できるようにすることで、基礎となる LoginModule はアプリケーションがユーザーと対話する様々な方法に依存しない状態を維持します。例えば GUI アプリケーションの CallbackHandler の実装は、ウィンドウを表示してユーザーの入力を求めることがあります。 一方でアプリケーションサーバーなど GUI でない環境の CallbackHandler 実装は、アプリケーションサーバー API を使用して単に資格情報を取得することがあります。CallbackHandler インターフェースには実装するメソッドが 1 つあります。
void handle(Callback[] callbacks)
    throws java.io.IOException, 
           UnsupportedCallbackException;
最後に説明する認証クラスは Callback インターフェースです。これは複数のデフォルト実装が提供されているタグ付けインターフェースで、前述の例で使用した NameCallbackPasswordCallback が含まれます。LoginModuleCallback を使用し、認証メカニズムで必要となる情報を要求します。LoginModule は認証のログインフェーズの間に Callback のアレイを直接 CallbackHandler.handle メソッドに渡します。callbackhandler がハンドルメソッドに渡された Callback オブジェクトの使用方法が分からない場合は、UnsupportedCallbackException をスローしてログイン呼び出しを中止します。