当初、Java認証・承認サービス(Java Authentication and Authorization Service: JAAS)は、Java 2 SDK, Standard Edition (J2SDK), v 1.3のオプション・パッケージでした。JAASは、J2SDK 1.4よりJava Standard Edition Development Kitに統合されています。
JAASは、認証されたアイデンティティにおけるサブジェクトベースの認証を提供します。このドキュメントでは、JAASの認証面、特にInterface LoginModuleに焦点を当てて解説します。
このドキュメントの対象読者
このドキュメントは、認証技術を実装するInterface LoginModuleを記述する必要がある、経験を積んだプログラマ向けに書かれています。
関連ドキュメント
このドキュメントでは、読者がすでに次のドキュメントを読んでいることを前提としています。
JAAS APIのさまざまなクラスおよびインタフェースについての説明も含まれます。詳細は、JAAS API仕様のJavadoc APIドキュメントを参照してください。
次のチュートリアルは、JAAS認証および承認を利用するすべてのユーザーを対象としています。
次のチュートリアルは、JAAS認証および承認のチュートリアルと似ていますが、Kerberos LoginModuleの使用方法の解説が含まれるため、使用する前にKerberosをインストールする必要があります。
この2つのチュートリアルは、認証とセキュアな通信のための基盤技術としてKerberosを利用するJAASおよびJava GSS-APIチュートリアルに含まれています。
LoginModuleは特定タイプの認証を提供するアプリケーションへプラグインされます。
Interface LoginModuleドキュメントでは、認証技術のプロバイダが実装する必要のあるインタフェースについて説明します。
アプリケーションはLoginContextアプリケーション・プログラミング・インタフェース(API)への書込みを実行するのに対し、認証技術のプロバイダはLoginModuleインタフェースを実装します。Configurationでは、特定のログイン・アプリケーションで使用されるLoginModuleを指定します。アプリケーション自体を変更せずに、複数の異なるLoginModuleをアプリケーションのプラグインとして使用できます。
LoginContextは、Configurationの読取りと特定のLoginModuleのインスタンス化を行います。各LoginModuleは、Subject、Interface CallbackHandler、共有のLoginModuleの状態、およびLoginModule固有のオプションを使用してインスタンス化されます。
Subjectは、認証中のユーザーまたはサービスを表し、認証が成功すると、関連するInterface Principalおよびクレデンシャルを保持するLoginModuleにより更新されます。LoginModuleは、loginメソッドの説明で述べるように、CallbackHandlerを使用してユーザーと通信します(ユーザー名とパスワードの入力を求めるためなど)。CallbackHandlerはnullも指定できることに注意してください。Subjectの認証にCallbackHandlerを必要とするLoginModuleは、nullのCallbackHandlerで初期化されていると、LoginExceptionをスローする場合があります。LoginModuleはオプションで共有状態を使用して、それら自体の間で情報やデータを共有できます。
LoginModule固有のオプションは、ログインConfiguration内で、このLoginModule用に構成されたオプションを表します。これらのオプションはLoginModule自体によって定義され、その中での動作を制御します。たとえば、LoginModuleでデバッグ/テスト機能をサポートするオプションを定義する場合を考えましょう。オプションは、debug=trueなどの、キーと値の構文を使用して定義されます。キーを使用して値を取得できるように、LoginModuleはオプションをMapとして格納します。LoginModuleが定義することを選択するオプションの数に制限はありません。
呼出し元アプリケーションは、認証プロセスをLoginContextのloginメソッド呼出しによって呼び出された単一の操作であると見なします。ただし、各LoginModule内部の認証プロセスは、明確な2つのフェーズにわかれています。認証の最初のフェーズでは、LoginContextのloginメソッドにより、Configuration内に指定された各LoginModuleのloginメソッドが呼び出されます。LoginModuleのloginメソッドは、実際の認証を実行してから(たとえば、パスワードの入力を促し、入力されたパスワードを検証する)、認証ステータスを非公開の状態情報として保存します。LoginModuleのloginメソッドは終了後に、true (成功した場合)またはfalse (無視する場合)を返すか、LoginExceptionをスローして障害を指定します。障害が発生した場合、LoginModuleは認証を再試行したり、遅延を招いたりしてはいけません。これらのタスクの実行は、アプリケーションが担当します。アプリケーションが認証の再実行を試みると、各LoginModuleのloginが再度呼び出されます。
次のフェーズでは、LoginContextの認証がすべて成功した(関連するrequired、requisite、sufficientおよびoptional LoginModuleのloginメソッドの呼出しに成功した)場合、各LoginModuleのcommitメソッドが呼び出されます。LoginModuleフラグ(required、requisite、sufficientおよびoptional)については、Configurationのドキュメント、およびJAASリファレンス・ガイドの付録B: ログイン構成の例を参照してください。LoginModuleのcommitメソッドは、非公開で保存された状態をチェックして、独自の認証が成功したかどうかを確認します。LoginContextの認証全体が成功し、LoginModule自体の認証が成功すると、commitメソッドにより、関連するPrincipal (認証済みの識別情報)およびクレデンシャル(暗号化鍵などの認証データ)がSubjectに関連付けられます。
LoginContextの認証全体が失敗した(関連するREQUIRED、REQUISITE、SUFFICIENTおよびOPTIONAL LoginModuleのloginメソッドが成功しなかった)場合、各LoginModuleのabortメソッドが呼び出されます。この場合、LoginModuleは、当初保存されていた認証状態をすべて削除/破棄します。
Subjectからのログアウトには、1つのフェーズのみが含まれます。LoginContextは、LoginModuleのlogoutメソッドを呼び出します。その後、LoginModuleのlogoutメソッドは、SubjectからのPrincipalまたはクレデンシャルの削除や、セッション情報のロギングなど、ログアウト処理を実行します。
次は、LoginModuleの実装およびテストに必要なステップです。
LoginModuleの適切なパッケージおよびクラス名を決定します。
たとえば、IBMにより開発されたLoginModuleにはcom.ibm.auth.Moduleと命名できます。ここで、com.ibm.authはパッケージ名、ModuleはLoginModuleクラス実装の名前です。
LoginModuleインタフェースは、実装を必要とする5つのabstractメソッドを指定します。
LoginModule.initializeメソッド
public void initialize (
Subject subject,
CallbackHandler handler,
Map<java.lang.String, ?> sharedState,
Map<java.lang.String, ?> options) { ... }
initializeメソッドが呼び出され、関連する認証および状態情報でLoginModuleの初期化が行われます。
このメソッドは、LoginModuleをインスタンス化したあと(かつほかのpublicメソッドを呼び出す前)、すぐにLoginContextにより呼び出されます。このメソッド実装は、将来の使用に備えて指定された引数を格納します。
initializeメソッドはさらに、指定されたsharedStateを調べて、他のLoginModuleから提供された追加の認証状態を特定し、指定されたoptionsも詳しく調べて、LoginModuleの動作に影響を与えるように指定された構成オプションを特定します。将来の使用に備えて、変数内にオプションの値を保存することもできます。
注意: JAAS LoginModuleは、PAM (use_first_pass、try_first_pass、use_mapped_passおよびtry_mapped_pass)に定義されたオプションを使用してシングル・サインオンします。詳細については、「Making Login Services Independent of Authentication Technologies」を参照してください。
次は、ログイン・モジュールがサポートする一般的なオプションの一覧です。ただし、これはガイドラインにすぎません。モジュールは、次のオプションのサブセットを随意サポートします。
try_first_pass - trueの場合、スタック内の最初のログイン・モジュールが入力されたパスワードを保存し、それ以降のログイン・モジュールはこれを使用しようとする。認証に失敗した場合、ログイン・モジュールは新しいパスワードを要求し、認証を再試行する。use_first_pass - trueの場合、スタック内の最初のログイン・モジュールが入力されたパスワードを保存し、それ以降のログイン・モジュールはこれを使用しようとする。ログイン・モジュールは、認証に失敗しても新しいパスワードを要求しない。try_mapped_pass - trueの場合、スタック内の最初のログイン・モジュールが入力されたパスワードを保存し、それ以降のログイン・モジュールはこれをサービス固有のパスワードにマッピングしようとする。認証に失敗した場合、ログイン・モジュールは新しいパスワードを要求し、認証を再試行する。use_mapped_pass - trueの場合、スタック内の最初のログイン・モジュールが入力されたパスワードを保存し、それ以降のログイン・モジュールはこれをサービス固有のパスワードにマッピングしようとする。ログイン・モジュールは、認証に失敗しても新しいパスワードを要求しない。moduleBanner - trueの場合、ログイン・モジュールは、CallBackHandlerの呼出し時に最初のCallbackとしてTextOutputCallbackを提供する。このCallbackには、認証を実行しているログイン・モジュールに関する情報が記述されている。debug - trueの場合、ログイン・モジュールに対してデバッグ情報の出力を指示する。initializeメソッドは、認識できない状態やオプションを無視できます。ただし、この種のイベントが発生した場合、それを記録する方が望ましい場合もあります。
このLoginModule (およびその他の構成済LoginModule)を呼び出すLoginContextはすべて、指定されたSubjectおよびsharedStateへの同一の参照を共有します。SubjectおよびsharedStateへの変更は、すべてが認識します。
LoginModule.loginメソッド
boolean login() throws LoginException;
loginメソッドが呼び出され、Subjectの認証が行われます。これが認証の第1フェーズです。
このメソッド実装は、実際の認証を実行します。たとえば、ユーザー名およびパスワードの入力を求めてから、パスワード・データベースに対しパスワードの検証を試みます。別のサンプル実装として、フィンガプリント・リーダーに指を挿入するようユーザーに指示し、入力されたフィンガ・プリントをフィンガプリント・データベースと照合する場合が考えられます。
LoginModuleがなんらかのユーザーとの通信の形式を必要とする(ユーザー名とパスワードの取得など)場合は、それを直接実行しないでください。それは、ユーザーとの様々な通信方法が存在するためであり、LoginModuleが様々なユーザーとの通信のタイプから独立させておくことが望ましいです。それよりも、LoginModuleのloginメソッドから、initializeメソッドに渡されたInterface CallbackHandlerのhandleメソッドを呼び出して、ユーザーと通信し、適切な結果(ユーザー名、パスワードなど)を設定するようにしてください。LoginModuleはCallbackHandlerに適切なCallback (ユーザー名に対してはNameCallback、パスワードに対してはPasswordCallback)からなる配列を渡し、CallbackHandlerは要求に従ってユーザーと通信し、Callback内に適切な値を設定します。たとえば、NameCallbackを処理する場合、CallbackHandlerはユーザーから名前を取得し、NameCallbackのsetNameメソッドを呼び出してその名前を格納します。
認証プロセスに、ネットワーク経由の通信が含まれる場合もあります。たとえば、このメソッド実装がKerberosのkinitと等価な機能を実行する場合、KDCとのコンタクトが必要になります。パスワード・データベースのエントリがリモート・ネーム・サービス内に存在する場合、Java Naming and Directory Interface (JNDI)を利用するなどして、そのネーム・サービスとコンタクトを取る必要があります。実装は、基盤となるオペレーティング・システムと通信する必要もあります。たとえば、ユーザーがSolaris、Linux、macOS、Windows NTなどのオペレーティング・システムにすでにログインしている場合、このメソッドは基盤となるオペレーティング・システムの識別情報を単にインポートします。
loginメソッドは、次の処理を行う必要があります。
LoginModuleを無視するかどうかを決定します。たとえば、ユーザーがこのLoginModuleと無関係な識別情報を使って認証を試みた場合(たとえば、ユーザーがNISを使い、rootで認証を試みた場合)は、LoginModuleを無視する必要があります。このLoginModuleを無視する必要がある場合、loginの戻り値はfalseになります。それ以外の場合、次の処理を行います。CallbackHandlerのhandleメソッドを呼び出します。commitメソッドで使用される可能性があります。trueを返し、認証に失敗した場合はLoginException (FailedLoginExceptionなど)をスローします。loginメソッド実装が、新規Principalまたはクレデンシャル情報を保存済みのSubjectオブジェクトに関連付けることはできません。このメソッドは、認証のみを実行して、認証結果および対応する認証状態を格納します。あとで、commitまたはabortメソッドからこの結果および状態へのアクセスが行われます。結果および状態は、ほかのLoginModuleと共有ではないので、通常、sharedState Mapには保存できません。
たとえば、LoginModuleがパスワードを共有するように構成されている場合であれば、このメソッドにとって、sharedStateのMapに状態情報を保存することは有利です。この場合、入力されたパスワードは、共有状態として保存されます。パスワードの共有により、パスワードを1回入力するだけで後続のLoginModuleでも認証された状態を維持できます。次に、sharedStateのMapから名前とパスワードを格納および保存する際の標準的な規約を示します。
javax.security.auth.login.name - 名前を保存/取得するための共有状態マップ・キーとして使用。javax.security.auth.login.password - パスワードを保存/取得するための共有状態マップ・キーとして使用。認証に失敗した場合、loginメソッドは認証を再試行できません。この処理はアプリケーションが担当します。LoginModule.login()内から何度もログインを試みるよりも、アプリケーションから繰り返しLoginContextのloginメソッドを呼び出すことをお薦めします。
LoginModule.commitメソッド
boolean commit() throws LoginException;
commitメソッドが呼び出され、認証プロセスがコミットされます。これは、第1認証フェーズが成功した場合の第2認証フェーズです。LoginContext全体の認証が成功した場合(関連するREQUIRED、REQUISITE、SUFFICIENT、およびOPTIONALのLoginModuleが成功した場合)に呼び出されます。
このメソッドは、loginメソッドにより保存された認証結果および対応する認証状態にアクセスします。
認証結果からloginメソッドの失敗が明らかになった場合、commitメソッドは最初に保存された対応する状態をすべて削除/破棄します。
保存された結果からLoginModuleのloginメソッドの成功が明らかになった場合は、対応する状態情報から関連するPrincipalおよびクレデンシャルの情報が構築されます。このPrincipalおよびクレデンシャルの情報が、initializeメソッドによって格納されたSubjectに追加されます。
Principalおよびクレデンシャルの追加後は、不要な状態フィールドを迅速に破棄する必要があります。たとえば、認証プロセスで格納されたユーザー名やパスワードは破棄されます。
commitメソッドは、コミットの成功または失敗を示す非公開の状態を保存する必要があります。
次に、LoginModuleのcommitメソッドから返される結果を図示します。各ボックスは、発生する可能性がある各種の状況を表します。たとえば、上部左側のボックスは、以前のloginメソッド呼出しとcommitメソッド自体の呼出しに成功した場合に返されるcommitメソッドの結果を示しています。
表6-1 LoginModule.commitメソッドの戻り値
| ログイン・ステータス | COMMIT: 成功 | COMMIT: 失敗 |
|---|---|---|
| LOGIN: 成功 | TRUEを返す | 例外をスローする |
| LOGIN: 失敗 | FALSEを返す | FALSEを返す |
LoginModule.abortメソッド
boolean abort() throws LoginException;
abortメソッドが呼び出され、認証プロセスが強制的に中断されます。これは、第1認証フェーズが失敗した場合の第2認証フェーズです。これは、LoginContextの全体の認証に失敗した場合に呼び出されます。
このメソッドは、最初に、loginメソッド(および場合によりcommitメソッド)によって保存されているLoginModuleの認証結果および対応する認証状態にアクセスし、情報を消去および破棄します。たとえば、ユーザー名およびパスワードは破棄されます。
LoginModuleの認証に失敗した場合、非公開の状態を消去する必要は生じません。
次に、LoginModuleのabortメソッドから返される結果を図示します。最初の図は、以前のlogin呼出しが成功したことを想定しています。たとえば、上部左側のボックスは、以前のloginメソッド呼出しとcommitメソッド呼出しに成功し、さらにabortメソッド自体の呼出しにも成功した場合に返されるabortメソッドの結果を示しています。
表6-2 ログイン成功時のLoginModule.abortメソッドの戻り値
| ログイン・ステータス | ABORT: 成功 | ABORT: 失敗 |
|---|---|---|
| COMMIT: 成功 | TRUEを返す | 例外をスローする |
| COMMIT: 失敗 | TRUEを返す | 例外をスローする |
LoginModule.logoutメソッド
boolean logout() throws LoginException;
logoutメソッドが呼び出され、Subjectからログアウトします。
このメソッドは、Principalを削除し、commit操作中にSubjectに関連付けられたクレデンシャルを削除/破棄します。このメソッドは、Subject内の既存のPrincipalやクレデンシャル、またはほかのLoginModuleによって追加されたPrincipalやクレデンシャルに対しては、一切の操作を実行できません。
Subjectが読取り専用 (SubjectのisReadOnlyメソッドがtrueを返す)の場合、このメソッドはcommit操作中にSubjectに関連付けられているクレデンシャルのみを破棄します(クレデンシャルを削除することはできません)。Subjectが読取り専用であり、commit操作中にSubjectと関連付けられたクレデンシャルを破棄できない(このクレデンシャルがDestroyableインタフェースを実装していない)場合、このメソッドはLoginExceptionをスローする可能性があります。
logoutメソッドは、ログアウトに成功した場合はtrueを返し、それ以外の場合はLoginExceptionをスローします。
テスト用として、既存のサンプル・アプリケーションを選択するか、新しいサンプル・アプリケーションを作成します。
アプリケーション要件、およびテストに使用できるサンプル・アプリケーションについては、Java Authentication and Authorization Service (JAAS)リファレンス・ガイドを参照してください。
LoginModuleのテストを準備します。
ステップ6a: LoginModuleとアプリケーション・コードをJARファイルに配置
ステップ6c: LoginModuleおよびアプリケーションのJARファイルのアクセス権の設定でポリシー内のJARファイルを参照できるように、LoginModuleおよびアプリケーション・コードを別々のJARファイルに格納します。次は、JARファイルを作成するサンプル・コマンドです。
jar cvf <JAR file name> <list of classes, separated by spaces>
このコマンドにより、指定されたクラスを含む、指定された名前のJARファイルが作成されます。
jarツールの詳細は、jarを参照してください。
ステップ6b: JARファイルの格納場所を決定
本来、アプリケーションはユーザーの好みの場所に格納できます。
LoginModuleも、ユーザーまたはその他のクライアントの好みの場所に格納できます。完全に信頼できるLoginModuleは、JREのlib/ext (標準拡張)ディレクトリに格納できます。
lib/extディレクトリ内にあるLoginModuleとその他のディレクトリにあるLoginModuleを両方ともテストする必要があります。これは、LoginModuleにセキュリティ関連操作の実行に必要なJava Development Kit (JDK)でのアクセス権を明示的に付与する必要がある場合と、そうでない場合があるためです。
JREのlib/extディレクトリ内のLoginModuleは、インストール済拡張機能と見なされるので、これにアクセス権を付与する必要はありません。インストール済拡張機能には、デフォルトのシステム・ポリシー・ファイル構文により、すべてのアクセス権が付与されます。
一方、それ以外のディレクトリ内のLoginModuleには、ポリシー・ファイル内のgrant文を使うなどしてアクセス権を付与する必要があります。
LoginModule JARファイルがインストール済み拡張機能と見なされない場合は、このファイルの格納場所を指定してテストします。次のステップでは、指定された場所にあるJARファイルにアクセス権を付与します。
ステップ6c: LoginModuleとアプリケーションのJARファイルにアクセス権を設定
LoginModuleやアプリケーションが、セキュリティ・チェックをトリガーするセキュリティ関連タスク(ネットワーク接続の確立、ローカル・ディスク上のファイルの読取り、書込みなど)を実行するとします。これらがインストール済拡張機能(ステップ6b: JARファイルの格納場所を決定を参照)ではなく、セキュリティ・マネージャがインストールされている環境で実行される場合は、Java Development Kit (JDK)でのアクセス権を付与する必要があります。
通常、LoginModuleはPrincipalとクレデンシャルを認証済サブジェクトに関連付けます。このため、LoginModuleは、アクセス権として、modifyPrincipals、modifyPublicCredentialsおよびmodifyPrivateCredentialsというターゲット名のAuthPermissionを必要とします。
次のコード例MyLM.jarには、LoginModuleにアクセス権を付与する文が含まれています。この種の文は、ポリシー・ファイルに記述されます。この例では、MyLM.jarファイルは/localWorkディレクトリに格納されるものとします。
grant codeBase "file:/localWork/MyLM.jar" {
permission javax.security.auth.AuthPermission "modifyPrincipals";
permission javax.security.auth.AuthPermission "modifyPublicCredentials";
permission javax.security.auth.AuthPermission "modifyPrivateCredentials";
};
注意:
LoginModule呼出しは、常にAccessController.doPrivileged呼出し内で行われるため、doPrivileged自体を呼び出す必要はありません。これを呼び出すと、意図せずにセキュリティ・ホールを作り出してしまう可能性があります。たとえば、LoginModuleがdoPrivileged呼出しの中でアプリケーション提供のCallbackHandlerを呼び出す場合、他の場合にはアクセス不可能なリソースへのアクセスがアプリケーションのCallbackHandlerに許可されるため、セキュリティ・ホールが生まれます。ステップ6d: ログイン・モジュールを参照する構成を作成
JAASはプラガブルな認証アーキテクチャをサポートするため、既存のアプリケーションを変更せずに新しいLoginModuleを使用できます。新しいLoginModuleの使用を示すためには、ログインConfigurationを更新するだけで済みます。
OracleのデフォルトのConfiguration実装は、ConfigFileで説明するように、構成ファイルから構成情報を読み取ります。
テスト用の構成ファイルを作成します。たとえば、以前取り上げた架空のIBMのアプリケーション用LoginModuleを構成する場合、次のような内容の構成ファイルを使用することになります。
AppName {
com.ibm.auth.Module REQUIRED debug=true;
};
AppNameは、アプリケーションがログイン構成ファイル内のこのエントリを参照するために使用する任意の名前です。アプリケーションはこの名前をLoginContextコンストラクタの第1引数として指定します。
アプリケーション、およびそれによるLoginModuleの使用をテストします。アプリケーションの実行時、使用するログイン構成ファイルを指定します。たとえば、MyApp.jarにMyAppという名前のアプリケーションが格納されていて、構成ファイルの名前がtest.confだとします。
この場合、次のようにしてアプリケーションを実行し、構成ファイルを指定できます。
java -classpath MyApp.jar -Djava.security.auth.login.config=test.conf MyApp
コマンド全体は、1行で入力してください。ここでは、読みやすくするために複数行に分けて表示してあります。
my.policyという名前のポリシー・ファイルを指定し、インストールされているセキュリティ・マネージャを使ってアプリケーションを実行するには、次のように入力します。
java -classpath MyApp.jar -Djava.security.manager -Djava.security.policy=my.policy -Djava.security.auth.login.config=test.conf MyApp
ここでも、コマンド全体は1行で入力してください。
debugオプションを付けてLoginModuleを構成すると、適正に動作することを確認できます。
コードをデバッグし、必要に応じてテストを続行します。問題が発生する場合は、上記のすべての手順が完了していることを確認してください。
ユーザーによる入力と、構成ファイルに指定したLoginModuleオプションを確実に変更してください。
異なったインストール・オプション(LoginModuleをインストール済み拡張機能にする、クラス・パス内に配置する、など)および実行環境(セキュリティ・マネージャを実行する、または実行しない)を使用するテストも実行してください。インストール・オプションの詳細は、ステップ6b: JARファイルの格納場所を決定を参照してください。特に、セキュリティ・マネージャがインストールされていてLoginModuleとアプリケーションがインストール済拡張機能でない場合は、LoginModuleを確実に動作させるため、ステップ6c: LoginModuleおよびアプリケーションのJARファイルのアクセス権の設定で説明されているように、必要なアクセス権を付与した後、インストールおよび実行環境をテストする必要があります。
LoginModuleまたはアプリケーションに変更が必要なことが判明した場合は、変更を行い、再コンパイルします(ステップ5: LoginModuleおよびアプリケーションのコンパイル)。LoginModuleのクライアント用ドキュメントを記述します。
LoginModule実装で使用する認証プロセス。LoginModuleのインストール方法。LoginModuleで有効な構成オプション。各オプションには、そのオプションの制御内容、オプション名、戻り値(または値の型)を指定。LoginModuleがインストール済拡張機能ではなく、セキュリティ・マネージャとともに実行される場合に必要なアクセス権。LoginModuleを参照するサンプルConfigurationファイル。LoginModuleに必要なアクセス権を付与するサンプル・ポリシー・ファイル。javadocコメントをソース・コード内に挿入すると、API javadocの生成が容易になる。