Java Authentication and Authorization Service (JAAS): LoginModule開発者ガイド
JAASは、認証されたアイデンティティにおけるサブジェクトベースの認証を提供します。このドキュメントでは、JAASの認証面、特にLoginModuleインタフェースに焦点を当てて解説します。
このドキュメントの対象読者
このドキュメントは、認証技術を実装するLoginModuleを記述する必要がある、経験を積んだプログラマ向けに書かれています。
関連ドキュメント
このドキュメントでは、読者がすでに次のドキュメントを読んでいることを前提としています。
JAAS APIのさまざまなクラスおよびインタフェースについての説明も含まれます。詳細は、JAAS API仕様のJavaDoc APIドキュメントを参照してください:
- javax.security.auth
- javax.security.auth.callback
- javax.security.auth.kerberos
- javax.security.auth.login
- javax.security.auth.spi
- javax.security.auth.x500
次のパッケージには、サポートされるLoginModuleの例が含まれています。
- com.sun.security.auth
- com.sun.security.auth.callback
- com.sun.security.auth.login
- com.sun.security.auth.module
次のチュートリアルは、JAAS認証および承認を利用するすべてのユーザーを対象としています。
次のチュートリアルは、JAAS認証および承認のチュートリアルと似ていますが、Kerberos LoginModuleの使用方法の解説が含まれるため、使用する前にKerberosをインストールする必要があります。
この2つのチュートリアルは、認証とセキュアな通信のための基盤技術としてKerberosを利用するJAASおよびJava GSS-APIチュートリアルの紹介に含まれています。
LoginModuleの概要
認証テクノロジ・プロバイダはLoginModuleインタフェースを実装する必要があります。LoginModuleは特定タイプの認証を提供するアプリケーションへプラグインされます。
アプリケーションはLoginContextアプリケーション・プログラミング・インタフェース(API)への書込みを実行するのに対し、認証技術のプロバイダはLoginModuleインタフェースを実装します。Configurationでは、特定のログイン・アプリケーションで使用されるLoginModuleを指定します。アプリケーション自体を変更せずに、複数の異なるLoginModuleをアプリケーションのプラグインとして使用できます。
LoginContextは、Configurationの読取りと特定のLoginModuleのインスタンス化を行います。各LoginModuleはSubject、CallbackHandler、共有のLoginModuleの状態、およびLoginModule固有のオプションを使用してインスタンス化されます。
Subjectは、認証中のユーザーまたはサービスを表し、認証が成功すると、関連する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「JAASログイン構成ファイル」を参照してください。)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の実装およびテストに必要なステップです。
ステップ2: LoginModule実装への命名
LoginModuleの適切なパッケージおよびクラス名を決定します。
たとえば、IBMにより開発されたLoginModuleにはcom.ibm.auth.Moduleと命名できます。ここで、com.ibm.authはパッケージ名、ModuleはLoginModuleクラス実装の名前です。
ステップ3: LoginModuleインタフェースの実装
LoginModuleインタフェースは、実装する必要のある5つの抽象メソッドを指定します。
LoginModule実装は、これらのメソッドに加え、引数をとらないpublicのコンストラクタを提供する必要があります。このことにより、LoginContextによるインスタンス化が適切に行われるようになります。LoginModule実装でこの種のコンストラクタが提供されない場合、引数を取らないデフォルト・コンストラクタが自動的にObjectクラスから継承されます。
ノート:
LoginModuleインタフェースを実装しない場合は、ログイン・モジュールを使用しようとするとLoginExceptionがスローされます。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の動作に影響を与えるように指定された構成オプションを特定します。将来の使用に備えて、変数内にオプションの値を保存することもできます。
次に示すのは、LoginModuleがサポートする一般的なオプションの一覧です。ただし、これはガイドラインにすぎません。モジュールは、次のオプションのサブセットを随意サポートします。
tryFirstPass-trueの場合、スタック内の最初のLoginModuleが入力されたパスワードを保存し、それ以降のLoginModulesもこれを使用することを試みます。認証に失敗した場合、LoginModuleは新しいパスワードを要求し、認証を再試行します。useFirstPass-trueの場合、スタック内の最初のLoginModuleが入力されたパスワードを保存し、それ以降のLoginModulesもこれを使用することを試みます。LoginModuleは、認証に失敗しても新しいパスワードを要求しません。tryMappedPass-trueの場合、スタック内の最初のLoginModuleが入力されたパスワードを保存し、それ以降のLoginModulesはこれをサービス固有のパスワードにマッピングすることを試みます。認証に失敗した場合、LoginModuleは新しいパスワードを要求し、認証を再試行します。useMappedPass-trueの場合、スタック内の最初のLoginModuleが入力されたパスワードを保存し、それ以降のLoginModulesはこれをサービス固有のパスワードにマッピングすることを試みます。LoginModuleは、認証に失敗しても新しいパスワードを要求しません。moduleBanner-trueの場合、LoginModuleは、CallbackHandlerの呼出し時に最初のCallbackとしてTextOutputCallbackを提供します。このCallbackには、認証を実行しているLoginModuleに関する情報が記述されます。debug-trueの場合、LoginModuleに対してデバッグ情報の出力を指示します。
initializeメソッドは、認識できない状態やオプションを無視できます。ただし、この種のイベントが発生した場合、それを記録する方が望ましい場合もあります。
このLoginModule (およびその他の構成済LoginModule)を呼び出すLoginContextはすべて、指定されたSubjectおよびsharedStateへの同一の参照を共有します。SubjectおよびsharedStateへの変更は、すべてが認識します。
LoginModule.loginメソッド
boolean login() throws LoginException;loginメソッドが呼び出され、Subjectの認証が行われます。これが認証の第1フェーズです。
このメソッド実装は、実際の認証を実行します。たとえば、ユーザー名およびパスワードの入力を求めてから、パスワード・データベースに対しパスワードの検証を試みます。別のサンプル実装として、フィンガプリント・リーダーに指を挿入するようユーザーに指示し、入力されたフィンガ・プリントをフィンガプリント・データベースと照合する場合が考えられます。
LoginModuleがなんらかのユーザーとの通信の形式を必要とする(ユーザー名とパスワードの取得など)場合は、それを直接実行しないでください。それは、ユーザーとの様々な通信方法が存在するためであり、LoginModuleが様々なユーザーとの通信のタイプから独立させておくことが望ましいです。それよりも、LoginModuleのloginメソッドから、initializeメソッドに渡されたCallbackHandlerインタフェースのhandleメソッドを呼び出して、ユーザーと通信し、適切な結果(ユーザー名、パスワードなど)を設定するようにしてください。LoginModuleはCallbackHandlerに適切なCallback (ユーザー名に対してはNameCallback 、パスワードに対してはPasswordCallback )からなる配列を渡し、CallbackHandlerは要求に従ってユーザーと通信し、Callback内に適切な値を設定します。たとえば、NameCallbackを処理する場合、CallbackHandlerはユーザーから名前を取得し、NameCallbackのsetNameメソッドを呼び出してその名前を格納します。
認証プロセスに、ネットワーク経由の通信が含まれる場合もあります。たとえば、このメソッド実装がKerberosのkinitと等価な機能を実行する場合、KDCとのコンタクトが必要になります。パスワード・データベースのエントリがリモート・ネーム・サービス内に存在する場合、Java Naming and Directory Interface (JNDI)を利用するなどして、そのネーム・サービスとコンタクトを取る必要があります。実装は、基盤となるオペレーティング・システムと通信する必要もあります。たとえば、ユーザーがLinux、macOS、Windowsなどのオペレーティング・システムにすでにログインしている場合、このメソッドは基盤となるオペレーティング・システムの識別情報を単にインポートします。
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- 名前を保存/取得するための共有状態マップ・キーとして使用。値はStringである必要があります。javax.security.auth.login.password- パスワードを保存/取得するための共有状態マップ・キーとして使用。値はchar配列である必要があります。
認証に失敗した場合、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-2 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呼出しが成功したことを想定しています。たとえば、abortメソッドは、以前のlogin呼出しとcommit呼出しに成功し、さらにabortメソッド自体も成功した場合は、TRUEを返します。
表6-3 ログイン成功時のLoginModule.abortメソッドの戻り値
| ログイン・ステータス | ABORT: 成功 | ABORT: 失敗 |
|---|---|---|
| COMMIT: 成功 | TRUEを返す | 例外をスローする |
| COMMIT: 失敗 | TRUEを返す | 例外をスローする |
2番目の図は、以前のloginメソッドの呼出しに失敗した場合に返されるLoginModuleのabortメソッドの結果を示しています。たとえば、abortメソッドは、以前のlogin呼出しには失敗し、以前のcommit呼出しには成功し、さらにabortメソッド自体も成功した場合は、FALSEを返します。
表6-4 ログイン失敗時のLoginModule.abortメソッドの戻り値
| ログイン・ステータス | ABORT: 成功 | ABORT: 失敗 |
|---|---|---|
| COMMIT: 成功 | FALSEを返す | FALSEを返す |
| COMMIT: 失敗 | FALSEを返す | FALSEを返す |
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をスローします。
ステップ4: サンプル・アプリケーションの選択または記述
テスト用として、既存のサンプル・アプリケーションを選択するか、新しいサンプル・アプリケーションを作成します。
アプリケーション要件、およびテストに使用できるサンプル・アプリケーションについては、Java Authentication and Authorization Service (JAAS)リファレンス・ガイドを参照してください。
ステップ6: テストの準備
ステップ6a: LoginModuleとアプリケーション・コードをJARファイルに配置
ステップ6b: LoginModuleとアプリケーションのJARファイルにアクセス権を設定でポリシー内のJARファイルを参照できるように、LoginModuleおよびアプリケーション・コードを別々のJARファイルに格納します。次は、JARファイルを作成するサンプル・コマンドです。
jar cvf <JAR file name> <list of classes, separated by spaces>
このコマンドにより、指定されたクラスを含む、指定された名前のJARファイルが作成されます。
jarツールの詳細は、jarを参照してください。
ステップ6b: LoginModuleとアプリケーションのJARファイルにアクセス権を設定
LoginModuleやアプリケーションが、セキュリティ・チェックをトリガーするセキュリティ関連タスク(ネットワーク接続の確立、ローカル・ディスク上のファイルの読取り、書込みなど)を実行するとします。セキュリティ・マネージャがインストールされている環境で実行する場合は、必要なアクセス権を付与する必要があります(JDKでのアクセス権を参照)。
警告:
セキュリティ・マネージャおよびそれに関連するAPIは非推奨であり、今後のリリースでは削除されます。セキュリティ・マネージャの代わりとなるものはありません。詳細および代替手段については、JEP 411を参照してください。通常、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に許可されるため、セキュリティ・ホールが生まれます。
ステップ6c: LoginModuleを参照する構成を作成
JAASはプラガブルな認証アーキテクチャをサポートするため、既存のアプリケーションを変更せずに新しいLoginModuleを使用できます。新しいLoginModuleの使用を示すためには、ログインConfigurationを更新するだけで済みます。
OracleのデフォルトのConfiguration実装は、ConfigFileで説明するように、構成ファイルから構成情報を読み取ります。
テスト用の構成ファイルを作成します。たとえば、以前取り上げた架空のIBMのアプリケーション用LoginModuleを構成する場合、次のような内容の構成ファイルを使用することになります。
AppName {
com.ibm.auth.Module REQUIRED debug=true;
};
AppNameは、アプリケーションがログイン構成ファイル内のこのエントリを参照するために使用する任意の名前です。アプリケーションはこの名前をLoginContextコンストラクタの第1引数として指定します。
ステップ7: LoginModuleの試用
アプリケーション、およびそれによる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行で入力してください。
警告:
セキュリティ・マネージャおよびそれに関連するAPIは非推奨であり、今後のリリースでは削除されます。セキュリティ・マネージャの代わりとなるものはありません。詳細および代替手段については、JEP 411を参照してください。debugオプションを付けてLoginModuleを構成すると、適正に動作することを確認できます。
コードをデバッグし、必要に応じてテストを続行します。問題が発生する場合は、ここまでのすべてのステップが完了していることを確認してください。
ユーザーによる入力と、構成ファイルに指定したLoginModuleオプションを確実に変更してください。
異なったインストール・オプション(LoginModuleをクラス・パスやモジュール・パス内に配置するなど)および実行環境(セキュリティ・マネージャを実行する、または実行しない)を使用するテストも実行してください。特に、セキュリティ・マネージャがインストールされている場合にLoginModuleを確実に動作させるには、ステップ6b: LoginModuleとアプリケーションのJARファイルにアクセス権を設定で説明されているように、必要なアクセス権を付与した後、インストールおよび実行環境をテストする必要があります。
- テスト中に、
LoginModuleまたはアプリケーションに変更が必要なことが判明した場合は、変更を行い、再コンパイルします(ステップ5: LoginModuleおよびアプリケーションのコンパイル)。 - 更新したコードをJARファイルに配置します(ステップ6: JARファイルへのLoginModuleおよびアプリケーション・コードの記述)。
- 必要な場合は、アクセス権を修正するか追加します(ステップ6b: LoginModuleとアプリケーションのJARファイルにアクセス権を設定)。
- 必要な場合は、ログイン構成ファイルを変更します(ステップ6c: LoginModuleを参照する構成を作成)。
- アプリケーションを再実行し、必要に応じてこれらのステップを繰り返します。