JAAS認証チュートリアル
JAASは、次の2つの目的で使用できます。
- ユーザーを認証する際、Javaコードがアプリケーション、アプレット、Bean、またはサーブレットであるかに関係なく、Javaコードを現在実行しているユーザーを確実かつセキュアに判定する。
- ユーザーを承認する際、アクションの実行に必要なアクセス制御権(アクセス権)をユーザーが保持していることを確認する。
このセクションでは、認証コンポーネントの基本的なチュートリアルを提供します。承認コンポーネントについては、「JAAS承認」チュートリアルで説明します。
JAAS認証は、プラガブルな方式で実行されます。つまり、Javaアプリケーションは、基盤となる認証技術から独立して機能します。新規または更新された技術を、アプリケーション自体を変更せずに、プラグインとして使用可能です。使用する特定の認証技術の実装は実行時に決定されます。その内容は、ログイン構成ファイルに指定します。このチュートリアルで使用する認証技術は、ユーザーが特定の名前とパスワードを指定しているかどうかを確認するだけの非常に基本的なものです。
このチュートリアルは、次のセクションで構成されます。
チュートリアルのコードを最初に実行してみる場合は、「コードの実行」を先に読んでから、その他のセクションに戻り、コードの記述および構成ファイルの詳細について学習してください。
認証チュートリアル・コード
このチュートリアルのコードは、3つのファイルで構成されます。
SampleAcn.java
には、サンプル・アプリケーション・クラス(SampleAcn
)とユーザー入力の処理に使用される別のクラス(MyCallbackHandler
)が含まれています。このチュートリアルでは、このファイル内のコードのみが理解する必要のあるコードです。アプリケーションでは、その他のソース・ファイルは間接的にのみ使用します。SampleLoginModule.java
は、JAAS認証チュートリアルのログイン構成ファイルで説明されているチュートリアルのログイン構成ファイル(sample_jass.config
)によって、目的の基礎となる認証を実装するクラスとして指定されるクラスです。SampleLoginModule
のユーザー認証は、ユーザーによって指定された名前とパスワードが特定の値を持っていることを単に検証する処理です。SamplePrincipal.java
は、java.security.Principalインタフェースを実装するサンプル・クラスです。SampleLoginModule
によって使用されます。
SampleAcn.java
認証チュートリアルのアプリケーション・コードは、単一のソース・ファイルSampleAcn.java
に含まれています。このファイルには次の2つのクラスが含まれています。
SampleAcnクラス
SampleAcn
クラスのmain
メソッドは、認証の実行後に、認証が成功したかどうかをレポートします。
ユーザーの認証用コードは、非常に簡潔です。次の2つのステップで構成されています。
最初に基本的なコードを紹介します。続いて、必要なimport文およびエラー処理を含む完全なSampleAcnクラスのコードを示します。
LoginContextのインスタンス化
ユーザーの認証には、まずjavax.security.auth.login.LoginContext
が必要です。次に、LoginContextをインスタンス化する基本的な方法を示します。
import javax.security.auth.login.*;
. . .
LoginContext lc =
new LoginContext(<config file entry name>,
<CallbackHandler to be used for user interaction>);
次に、このチュートリアル・コードによるインスタンス化の実行方法を具体的に示します。
import javax.security.auth.login.*;
. . .
LoginContext lc =
new LoginContext("Sample",
new MyCallbackHandler());
引数について、次に説明します。
- JAASログイン構成ファイルのエントリ名
これは、LoginContextが、ログイン構成に説明されているJAASログイン構成ファイル内でこのアプリケーションのエントリ検索に使用する名前です。このようなエントリは、基盤となる適切な認証テクノロジを実装するクラスを指定します。クラスは、
javax.security.auth.spi
パッケージ内のLoginModuleインタフェースを実装する必要があります。このサンプル・コードでは、このチュートリアルに付属している
SampleLoginModule
を使用します。SampleLoginModule
は、ユーザーが特定の名前およびパスワードを入力したかどうかを確認することによって認証を実行します。このチュートリアルで使用するログイン構成ファイル(
sample_jass.config
)のエントリ(JAAS認証チュートリアルのログイン構成ファイルを参照)は、「Sample」という名前です。LoginContextコンストラクタの最初の引数には、この名前を指定してください。 - CallbackHandlerのインスタンス
LoginModuleがユーザーと通信する必要がある場合(たとえばユーザー名やパスワードの入力を求める場合)、それは直接には行われません。それは、ユーザーとの様々な通信方法が存在するためであり、LoginModuleが様々なユーザーとの通信のタイプから独立させておくことが望ましいです。かわりに、LoginModuleは、javax.security.auth.callback.CallbackHandlerを呼び出してユーザーとの通信を実行し、ユーザー名やパスワードなどの要求された情報を取得します。
使用する特定のCallbackHandlerのインスタンスを、LoginContextコンストラクタの2番目の引数として指定します。LoginContextは、このインスタンスを基盤となるLoginModule (ここでは
SampleLoginModule
)に転送します。通常、アプリケーションは、固有のCallbackHandler実装を提供します。単純なCallbackHandlerであるTextCallbackHandlerは、com.sun.security.auth.callback
パッケージに提供されており、コマンド行に情報を出力し、入力を読み取ります。ただし、実際に紹介するのは、固有のCallbackHandler実装を提供するアプリケーションの典型例です(MyCallbackHandlerクラスを参照)。
LoginContextのloginメソッドの呼び出し
これでLoginContext lc
を保持できたので、login
メソッドを呼び出して認証処理を実行します。
lc.login();
LoginContextは、新しい空のjavax.security.auth.Subjectオブジェクト(認証されるユーザーまたはサービスを表す)をインスタンス化します(サブジェクトを参照)。LoginContextは、構成済のLoginModule (この例の場合はSampleLoginModule
)を構築し、この新しいサブジェクトとMyCallbackHandler
を使用してインスタンス化します。
次に、LoginContextのlogin
メソッドによってSampleLoginModule
内のメソッドが呼び出され、ログインおよび認証が実行されます。SampleLoginModule
は、MyCallbackHandler
を使用してユーザー名とパスワードを取得します。次に、SampleLoginModule
は名前とパスワードが適切であるかどうかをチェックします。
認証に成功した場合、SampleLoginModule
は、ユーザーを表すプリンシパルをサブジェクトに追加します。SampleLoginModule
によってサブジェクトに追加されるプリンシパルは、SamplePrincipal
(java.security.Principalインタフェースを実装するサンプル・クラス)のインスタンスです。
その後、呼出し側アプリケーションは、LoginContextのgetSubject
メソッドを呼び出し(このステップは、このチュートリアルではオプション)、認証されたサブジェクトを取得します。
完全なSampleAcnクラスのコード
ここまでで、ユーザーの認証に必要な基本コードを見てきました。ここで、すべてを統合して、関連するimport文およびエラー処理を含む完全なクラスをSampleAcn.java
内に作成できます。
SampleAcn.java
package sample;
import java.io.*;
import java.util.*;
import javax.security.auth.login.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;
/**
* This Sample application attempts to authenticate a user
* and reports whether or not the authentication was successful.
*/
public class SampleAcn {
/**
* Attempt to authenticate the user.
*
* @param args input arguments for this application. These are ignored.
*/
public static void main(String[] args) {
// Obtain a LoginContext, needed for authentication. Tell it
// to use the LoginModule implementation specified by the
// entry named "Sample" in the JAAS login configuration
// file and to also use the specified CallbackHandler.
LoginContext lc = null;
try {
lc = new LoginContext("Sample", new MyCallbackHandler());
} catch (LoginException le) {
System.err.println("Cannot create LoginContext. "
+ le.getMessage());
System.exit(-1);
} catch (SecurityException se) {
System.err.println("Cannot create LoginContext. "
+ se.getMessage());
System.exit(-1);
}
// the user has 3 attempts to authenticate successfully
int i;
for (i = 0; i < 3; i++) {
try {
// attempt authentication
lc.login();
// if we return with no exception, authentication succeeded
break;
} catch (LoginException le) {
System.err.println("Authentication failed:");
System.err.println(" " + le.getMessage());
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
// ignore
}
}
}
// did they fail three times?
if (i == 3) {
System.out.println("Sorry");
System.exit(-1);
}
System.out.println("Authentication succeeded!");
}
}
/**
* The application implements the CallbackHandler.
*
* <p> This application is text-based. Therefore it displays information
* to the user using the OutputStreams System.out and System.err,
* and gathers input from the user using the InputStream System.in.
*/
class MyCallbackHandler implements CallbackHandler {
/**
* Invoke an array of Callbacks.
*
* <p>
*
* @param callbacks an array of <code>Callback</code> objects which contain
* the information requested by an underlying security
* service to be retrieved or displayed.
*
* @exception java.io.IOException if an input or output error occurs. <p>
*
* @exception UnsupportedCallbackException if the implementation of this
* method does not support one or more of the Callbacks
* specified in the <code>callbacks</code> parameter.
*/
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " +
toc.getMessageType());
}
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(System.console().readPassword());
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}
MyCallbackHandlerクラス
LoginModuleがユーザーと通信して、認証情報を取得する必要がある場合があります。LoginModuleは、この目的でjavax.security.auth.callback.CallbackHandler
を使用します。アプリケーションは、com.sun.security.auth.callback
パッケージに提供されたいずれかのサンプル実装を使用するか、より一般的に、CallbackHandler実装を作成できます。アプリケーションは、LoginContextのインスタンス化に引数としてCallbackHandlerを渡します。LoginContextは、このCallbackHandlerを、基礎となるLoginModuleに直接転送します。
このチュートリアルのサンプル・コードでは、独自のCallbackHandler実装(内の
MyCallbackHandler
クラス)を使用します。
CallbackHandlerは、1つのメソッドを実装するインタフェースです。
void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException;
このLoginModuleは、CallbackHandlerのhandleメソッドに適切なjavax.security.auth.callback.Callbackの配列(ユーザー名の場合はNameCallback、パスワードの場合はPasswordCallbackなど)を渡します。CallbackHandlerは、要求に従ってユーザーと通信し、Callback内に適切な値を設定します。
MyCallbackHandler
のhandle
メソッドの構築方法は次のとおりです。
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display a message according to a specified type
. . .
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
. . .
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for a password
. . .
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
CallbackHandlerのhandleメソッドは、特定の型のCallbackインスタンス(NameCallback、PasswordCallbackなど)の配列を受け取ります。このメソッドは、アプリケーションの実行に適した方法でユーザーと通信し、各Callbackを処理します。
MyCallbackHandler
は、3つのタイプのコールバック(ユーザーにユーザー名の入力を求めるNameCallback、パスワードの入力を求めるPasswordCallback、SampleLoginModuleからユーザーに送信するメッセージ(エラー、警告など)を報告するTextOutputCallback)を処理します。
handleメソッドは、報告するメッセージを抽出し、System.out
に出力することにより、TextOutputCallbackを処理します。オプションで、追加の文字列(メッセージ・タイプによって異なる)が前に付けられます。報告されるメッセージは、TextOutputCallbackのgetMessageメソッドを呼び出すことによって決定され、タイプは、getMessageTypeメソッドを呼び出すことによって決定されます。次は、TextOutputCallbackを処理するコードです。
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " +
toc.getMessageType());
}
handle
メソッドは、ユーザーにユーザー名の入力を求めることによってNameCallbackを処理します。まず、System.err
にプロンプトを出力します。次に、NameCallbackのsetName
メソッドを呼び出し、ユーザーによって入力された名前を渡すことにより、SampleLoginModule
によって使用される名前を設定します。
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
同様に、handle
メソッドはPasswordCallbackを処理するときも、System.err
にパスワードの入力を求めるプロンプトを出力します。次に、PasswordCallbackのsetPassword
メソッドを呼び出し、ユーザーによって入力されたパスワードを渡すことにより、SampleLoginModule
によって使用されるパスワードを設定します。
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(System.console().readPassword());
SampleLoginModule.javaとSamplePrincipal.java
SampleLoginModule.java
はLoginModule
インタフェースを実装します。SampleLoginModule
は、チュートリアルのログイン構成ファイル(JAAS認証チュートリアルのログイン構成ファイルを参照)によって、目的の基礎となる認証を実装するクラスとして指定されるクラスです。SampleLoginModule
のユーザー認証は、ユーザーによって指定された名前とパスワードが特定の値を持っていることを単に検証する処理です。このSampleLoginModule
は、チュートリアルのログイン構成ファイルにより、LoginModuleとして指定されます。これは、(1)あらゆる環境に適切な基本タイプの認証を実行するため、すべてのユーザーのチュートリアルで使用でき、(2)認証技術を実装するLoginModuleを記述できる必要がある上級プログラマ向けのLoginModuleの実装例を提供するからです。
SamplePrincipal.java
は、java.security.Principalインタフェースを実装するサンプル・クラスです。認証に成功した場合、SampleLoginModule
は、ユーザーを表すSamplePrincipal
をサブジェクトに追加します。
重要: アプリケーションの作成者は、LoginModuleやプリンシパル実装の記述方法を理解していなくてもかまいません。SampleLoginModule
やSamplePrincipal
のコードを確認する必要はありません。アプリケーションの作成と構成情報(ログイン構成ファイルの内容など)の指定に集中し、アプリケーションが構成によって指定されたLoginModuleを利用してユーザーを認証できるようにしてください。使用するLoginModuleを決定し、LoginModuleのドキュメントからLoginModuleの動作を制御するために(構成内に)値を指定できるオプションを確認してください。
どのベンダーの提供するログイン・モジュール実装でも使用可能です。「付録B: JAASログイン構成ファイル」に記載されているように、いくつかの実装がOracleのJDKに同梱されています。
LoginModuleを記述する必要があるプログラマ向けの情報は、Java Authentication and Authorization Service (JAAS): LoginModule開発者ガイドで確認できます。
SampleLoginModule.java
package sample.module;
import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import sample.principal.SamplePrincipal;
/**
* <p> This sample LoginModule authenticates users with a password.
*
* <p> This LoginModule only recognizes one user: testUser
* <p> testUser's password is: testPassword
*
* <p> If testUser successfully authenticates itself,
* a <code>SamplePrincipal</code> with the testUser's user name
* is added to the Subject.
*
* <p> This LoginModule recognizes the debug option.
* If set to true in the login Configuration,
* debug messages will be output to the output stream, System.out.
*
*/
public class SampleLoginModule implements LoginModule {
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
private Map sharedState;
private Map options;
// configurable option
private boolean debug = false;
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
// username and password
private String username;
private char[] password;
// testUser's SamplePrincipal
private SamplePrincipal userPrincipal;
/**
* Initialize this <code>LoginModule</code>.
*
* @param subject the <code>Subject</code> to be authenticated. <p>
*
* @param callbackHandler a <code>CallbackHandler</code> for communicating
* with the end user (prompting for user names and
* passwords, for example). <p>
*
* @param sharedState shared <code>LoginModule</code> state. <p>
*
* @param options options specified in the login
* <code>Configuration</code> for this particular
* <code>LoginModule</code>.
*/
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<java.lang.String, ?> sharedState,
Map<java.lang.String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
// initialize any configured options
debug = "true".equalsIgnoreCase((String)options.get("debug"));
}
/**
* Authenticate the user by prompting for a user name and password.
*
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*
* @exception FailedLoginException if the authentication fails. <p>
*
* @exception LoginException if this <code>LoginModule</code>
* is unable to perform the authentication.
*/
public boolean login() throws LoginException {
// prompt for a user name and password
if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("user name: ");
callbacks[1] = new PasswordCallback("password: ", false);
try {
callbackHandler.handle(callbacks);
username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword == null) {
// treat a NULL password as an empty password
tmpPassword = new char[0];
}
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0,
password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information " +
"from the user");
}
// print debugging information
if (debug) {
System.out.println("\t\t[SampleLoginModule] " +
"user entered user name: " +
username);
System.out.print("\t\t[SampleLoginModule] " +
"user entered password: ");
for (int i = 0; i < password.length; i++)
System.out.print(password[i]);
System.out.println();
}
// verify the username/password
boolean usernameCorrect = false;
boolean passwordCorrect = false;
if (username.equals("testUser"))
usernameCorrect = true;
if (usernameCorrect &&
password.length == 12 &&
password[0] == 't' &&
password[1] == 'e' &&
password[2] == 's' &&
password[3] == 't' &&
password[4] == 'P' &&
password[5] == 'a' &&
password[6] == 's' &&
password[7] == 's' &&
password[8] == 'w' &&
password[9] == 'o' &&
password[10] == 'r' &&
password[11] == 'd') {
// authentication succeeded!!!
passwordCorrect = true;
if (debug)
System.out.println("\t\t[SampleLoginModule] " +
"authentication succeeded");
succeeded = true;
return true;
} else {
// authentication failed -- clean out state
if (debug)
System.out.println("\t\t[SampleLoginModule] " +
"authentication failed");
succeeded = false;
username = null;
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
if (!usernameCorrect) {
throw new FailedLoginException("User Name Incorrect");
} else {
throw new FailedLoginException("Password Incorrect");
}
}
}
/**
* This method is called if the LoginContext's
* overall authentication succeeded
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* succeeded).
*
* If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> method), then this method associates a
* <code>SamplePrincipal</code>
* with the <code>Subject</code> located in the
* <code>LoginModule</code>. If this LoginModule's own
* authentication attempted failed, then this method removes
* any state that was originally saved.
*
* @exception LoginException if the commit fails.
*
* @return true if this LoginModule's own login and commit
* attempts succeeded, or false otherwise.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
} else {
// add a Principal (authenticated identity)
// to the Subject
// assume the user we authenticated is the SamplePrincipal
userPrincipal = new SamplePrincipal(username);
if (!subject.getPrincipals().contains(userPrincipal))
subject.getPrincipals().add(userPrincipal);
if (debug) {
System.out.println("\t\t[SampleLoginModule] " +
"added SamplePrincipal to Subject");
}
// in any case, clean out state
username = null;
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
commitSucceeded = true;
return true;
}
}
/**
* This method is called if the LoginContext's
* overall authentication failed.
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* did not succeed).
*
* If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> and <code>commit</code> methods),
* then this method cleans up any state that was originally saved.
*
* @exception LoginException if the abort fails.
*
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout the user.
*
* This method removes the <code>SamplePrincipal</code>
* that was added by the <code>commit</code> method.
*
* @exception LoginException if the logout fails.
*
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*/
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
succeeded = false;
succeeded = commitSucceeded;
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
return true;
}
}
SamplePrincipal.java
package sample.principal;
import java.security.Principal;
/**
* This class implements the <code>Principal</code> interface
* and represents a Sample user.
*
* Principals such as this <code>SamplePrincipal</code>
* may be associated with a particular <code>Subject</code>
* to augment that <code>Subject</code> with an additional
* identity. Refer to the <code>Subject</code> class for more information
* on how to achieve this. Authorization decisions can then be based upon
* the Principals associated with a <code>Subject</code>.
*
* @see java.security.Principal
* @see javax.security.auth.Subject
*/
public class SamplePrincipal implements Principal, java.io.Serializable {
/**
* @serial
*/
private String name;
/**
* Create a SamplePrincipal with a Sample username.
*
* @param name the Sample username for this user.
*
* @exception NullPointerException if the <code>name</code>
* is <code>null</code>.
*/
public SamplePrincipal(String name) {
if (name == null)
throw new NullPointerException("illegal null input");
this.name = name;
}
/**
* Return the Sample username for this <code>SamplePrincipal</code>.
*
* @return the Sample username for this <code>SamplePrincipal</code>
*/
public String getName() {
return name;
}
/**
* Return a string representation of this <code>SamplePrincipal</code>.
*
* @return a string representation of this <code>SamplePrincipal</code>.
*/
public String toString() {
return("SamplePrincipal: " + name);
}
/**
* Compares the specified Object with this <code>SamplePrincipal</code>
* for equality. Returns true if the given object is also a
* <code>SamplePrincipal</code> and the two SamplePrincipals
* have the same username.
*
* @param o Object to be compared for equality with this
* <code>SamplePrincipal</code>.
*
* @return true if the specified Object is equal equal to this
* <code>SamplePrincipal</code>.
*/
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof SamplePrincipal))
return false;
SamplePrincipal that = (SamplePrincipal)o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
/**
* Return a hash code for this <code>SamplePrincipal</code>.
*
* @return a hash code for this <code>SamplePrincipal</code>.
*/
public int hashCode() {
return name.hashCode();
}
}
ログイン構成
JAAS認証はプラガブルな形式で実行されるため、アプリケーションは、基盤となる認証技術から独立した状態を維持できます。システム管理者は、各アプリケーションで使用する認証技術(LoginModules)を決定し、ログイン構成内に構成します。構成情報のソース(ファイルやデータベース)は、現在のjavax.security.auth.login.Configuration実装によって異なります。OracleのデフォルトのConfiguration
実装は、ConfigFileクラスで説明するように、構成ファイルから構成情報を読み取ります。
ログイン構成ファイルとその内容、および使用するログイン構成ファイルの指定方法の詳細は、付録B: JAASログイン構成ファイルを参照してください。
JAAS認証チュートリアルのログイン構成ファイル
すでに説明したように、このチュートリアルで使用するログイン構成ファイルsample_jass.config
には、次のエントリのみが含まれます
Sample {
sample.module.SampleLoginModule required debug=true;
};
このエントリの名前は「Sample」であり、チュートリアル・アプリケーションSampleAcn
がエントリを参照するために、使用する名前です。このエントリは、ユーザー認証の実行に使用するLoginModuleがsample.module
パッケージ内のSampleLoginModule
であること、および認証が成功したと見なされるためにはこのSampleLoginModule
が「成功する」必要があることを示します。SampleLoginModule
は、ユーザーから提供された名前とパスワードが期待したもの(それぞれ「testUser」と「testPassword」)である場合にかぎり成功します。
SampleLoginModule
は「debug」オプションも定義します(true
に設定可能)。このオプションの値をtrue
に設定すると、SampleLoginModule
により、認証の進捗に関する追加情報が出力されます。LoginModuleで定義できるオプションの数に制限はありません。LoginModuleのドキュメントには、構成ファイル内に設定可能なオプションの名前と値が記載されています。
コードの実行
JAAS認証チュートリアル・コードを実行するには、次の操作を行う必要があります。
-
次のファイルを1つのディレクトリ内に格納します。
- sample_jass.configログイン構成ファイル(JAAS認証チュートリアルのログイン構成ファイルを参照)
-
最上位ディレクトリの下にsampleという名前のサブディレクトリを作成し、ここに次のものを格納します(ノート:
SampleAcn.java
内のSampleAcn
およびMyCallbackHandlerクラスはsample
パッケージ内にある)。SampleAcn.java
アプリケーション・ソース・ファイル
-
sample
ディレクトリのサブディレクトリを作成し、module
という名前を付けます。ここに次のものを格納します(ノート:SampleLoginModule
クラスはsample.module
パッケージ内にある)。SampleLoginModule.java
ソース・ファイル
sample
ディレクトリのサブディレクトリをもう1つ作成し、principal
という名前を付けます。ここに次のものを格納します(ノート:SamplePrincipal
クラスはsample.principal
パッケージ内にある)。SamplePrincipal.java
ソース・ファイル
-
最上位のディレクトリで、
SampleAcn.java
、SampleLoginModule.java
、SamplePrincipal.java
をコンパイルします。javac sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java
コマンド全体を1行に入力してください。
- 次を指定して、
SampleAcn
アプリケーションを実行します-Djava.security.auth.login.config==sample_jaas.config
。使用するログイン構成ファイルとしてsample_jaas.config
を指定します。
完全なコマンドは次のとおりです:
java -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
ノート:
java.security.auth.login.config
システム・プロパティで、(等号を2つ(==
)ではなく)等号を1つ(=
)使用している場合、このシステム・プロパティおよびjava.security
ファイルの両方で指定された構成が使用されます。ユーザー名とパスワードの入力が求められ、ログイン構成ファイルに指定されたSampleLoginModule
により、これらが正しいことが確認されます。SampleLoginModule
は、ユーザー名にtestUser
、パスワードにtestPassword
を期待します。
debug
オプション(ログイン構成ファイル内でtrue
に設定)の結果として、SampleLoginModule
によりメッセージが出力されます。ログインに成功すると、SampleAcnにより、次のメッセージが表示されます。
Authentication succeeded!
ログインに失敗した場合(パスワードのスペル・ミスなどにより)、次のメッセージが表示されます。
Authentication failed:
このあとに、失敗の原因が示されます。たとえば、パスワードのスペル・ミスがあった場合、次のようなメッセージが表示されます。
Authentication failed:
Password Incorrect
SampleAcnのログインの最大試行回数は3回です。
セキュリティ・マネージャを使用したコードの実行
セキュリティ・マネージャがインストールされた環境でJavaプログラムを実行する場合、有効なセキュリティ・ポリシーによりアクセス権が明示的に付与されているのでないかぎり、リソースへのアクセスやセキュリティ関連操作の実行は許可されません。(JDKでのアクセス権を参照。)アクセス権をポリシー・ファイル内のエントリによって付与する必要があります(デフォルトのPolicyの実装とポリシー・ファイルの構文を参照。)
警告:
セキュリティ・マネージャおよびそれに関連するAPIは非推奨であり、今後のリリースでは削除されます。セキュリティ・マネージャの代わりとなるものはありません。詳細および代替手段については、JEP 411を参照してください。大半のブラウザはセキュリティ・マネージャをインストールするため、一般にアプレットは、セキュリティ・マネージャの監視下で実行されます。一方、アプリケーションでは、アプリケーションの実行時にセキュリティ・マネージャは自動的にインストールされないため、そのようなことはありません。このため、SampleAcn
などのアプリケーションでは、デフォルトでリソースへのフル・アクセス権を持ちます。
セキュリティ・マネージャを使用してアプリケーションを実行するには、コマンド行に-Djava.security.manager
引数を含めてインタプリタを呼び出すだけです。
ポリシー・ファイルは指定せずに、セキュリティ・マネージャを使用してSampleAcn
を呼び出そうすると(必要なアクセス権またはAllPermission
を付与するデフォルト・ポリシー設定をほかの場所で保持しないかぎり)、次のメッセージが表示されます。
% java -Djava.security.manager \
-Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
Exception in thread "main" java.security.AccessControlException:
access denied (
javax.security.auth.AuthPermission createLoginContext.Sample)
LoginContextの作成に必要なアクセス権をコードに付与するポリシー・ファイルが作成および使用されていないため、上に示すようにAccessControlExceptionが表示されます。
セキュリティ・マネージャをインストールした環境でSampleAcn
アプリケーションを実行するために必要なすべてのステップを、次に示します。「コードの実行」で説明したステップを実行済みの場合は、最初の5つのステップを省略できます。
-
次のファイルを1つのディレクトリ内に格納します。
sample_jass.config
ログイン構成ファイル(JAAS認証チュートリアルのログイン構成ファイルを参照)
-
最上位ディレクトリの下にsampleという名前のサブディレクトリを作成し、ここに次のものを格納します(ノート:
SampleAcn.java
内のSampleAcn
およびMyCallbackHandlerクラスはsample
パッケージ内にある)。SampleAcn.java
アプリケーション・ソース・ファイル
-
sample
ディレクトリのサブディレクトリを作成し、module
という名前を付けます。ここに次のものを格納します(ノート:SampleLoginModule
クラスはsample.module
パッケージ内にある)。SampleLoginModule.java
ソース・ファイル
-
sample
ディレクトリのサブディレクトリをもう1つ作成し、principal
という名前を付けます。ここに次のものを格納します(ノート:SamplePrincipal
クラスはsample.principal
パッケージ内にある)。SamplePrincipal.java
ソース・ファイル
-
最上位のディレクトリで、
SampleAcn.java
、SampleLoginModule.java
、SamplePrincipal.java
をコンパイルします。javac sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java
コマンド全体を1行に入力してください。
-
SampleAcn.class
とMyCallbackHandler.class
を含むJARファイルを作成します。jar -cvf SampleAcn.jar sample/SampleAcn.class sample/MyCallbackHandler.class
コマンド全体を1行に入力してください。このコマンドにより、JARファイル
SampleAcn.jar
が作成され、その内部にSampleAcn.class
とMyCallbackHandler.class
が格納されます。 -
SampleLoginModule.class
とSamplePrincipal.class
を含むJARファイルを作成します。jar -cvf SampleLM.jar sample/module/SampleLoginModule.class sample/principal/SamplePrincipal.class
-
適切なアクセス権を付与するポリシー・ファイルを作成します。
LoginContextのインスタンス化を実行するコードに必要なアクセス権は、
createLoginContext.<entry name>
をターゲットとするjavax.security.auth.AuthPermission
です。ここで、<entry name>
は、アプリケーションがLoginContextのインスタンス化で参照する、ログイン構成ファイルのエントリ名です。コード内に示されているように、SampleAcn
アプリケーションがLoginContextのインスタンス化で使用する名前は、Sample
です。LoginContext lc = new LoginContext("Sample", new MyCallbackHandler());
このため、次のアクセス権を
SampleAcn.jar
に付与する必要があります。permission javax.security.auth.AuthPermission "createLoginContext.Sample";
SampleLM.jar
ファイルにもアクセス権を付与する必要があります。付与する必要があるアクセス権を判断するには、LoginModuleのドキュメントを参照してください。SampleLoginModule
の場合、サブジェクトにプリンシパルを追加するには、modifyPrincipals
をターゲットとするjavax.security.auth.AuthPermission
が必要です。permission javax.security.auth.AuthPermission "modifyPrincipals";
ポリシー・ファイル
sampleacn.policy
を、SampleAcn.java
などの格納先ディレクトリにコピーします。これは、次のgrant
文を含むポリシー・ファイルで、SampleAcn.jar
(現在のディレクトリ内)に必要なアクセス権を付与します。grant codebase "file:./SampleAcn.jar" { permission javax.security.auth.AuthPermission "createLoginContext.Sample"; };
ポリシー・ファイルには、
SampleLM.jar
(カレント・ディレクトリにもある)に適切なアクセス権を付与する次のようなgrant
文が含まれています。grant codebase "file:./SampleLM.jar" { permission javax.security.auth.AuthPermission "modifyPrincipals"; };
ノート: ポリシー・ファイルおよびその内部のエントリ構造については、「デフォルトのPolicyの実装とポリシー・ファイルの構文」を参照してください。アクセス権は、JDKでのアクセス権で説明されています。
次を指定して、
SampleAcn
アプリケーションを実行します- 適切な
-classpath
節(SampleAcn.jar
およびSampleLM.jar
JARファイル内のクラスを検索するため)。 -Djava.security.manager
。セキュリティ・マネージャのインストールを指定します。-Djava.security.policy==sampleacn.policy
。使用するポリシー・ファイルとしてsampleacn.policy
を指定します。-Djava.security.auth.login.config==sample_jaas.config
。使用するログイン構成ファイルとしてsample_jaas.config
を指定します。
ノート:
組込みのJDKポリシー・ファイルは、JDKの構成がすぐに使用できるセキュアなものであるよう設計されたデフォルトの権限セットを付与しますが、
java.security.policy
プロパティで等号を2つ(==
)使用すると、このファイルがオーバーライドされるため、使用する際は注意が必要です。このポリシーをオーバーライドすると予期しない動作が発生する(JDKコードに正しい権限が付与されない)可能性があるため、経験豊富なユーザーのみが実行するようにしてください。java.security.auth.login.config
システム・プロパティで、(等号を2つ(==
)ではなく)等号を1つ(=
)使用している場合、このシステム・プロパティおよびjava.security
ファイルの両方で指定された構成が使用されます。次に、Windows、LinuxおよびmacOSで使用する完全なコマンドを示します。クラス・パス項目の区切りとして、LinuxおよびmacOSシステムではコロンを使用するのに対し、Windowsシステムではセミコロンを使用する点のみが異なります。
次にWindowsの全コマンドを示します。
java -classpath SampleAcn.jar;SampleLM.jar -Djava.security.manager -Djava.security.policy==sampleacn.policy -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
次に、LinuxおよびmacOSの全コマンドを示します。
java -classpath SampleAcn.jar:SampleLM.jar -Djava.security.manager -Djava.security.policy==sampleacn.policy -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
コマンド全体は、1行で入力してください。ここでは、読みやすくするために複数行に分けて表示してあります。システムに対しコマンドが長すぎる場合は、
.bat
ファイル(Windowsの場合)または.sh
ファイル(LinuxおよびmacOSの場合)に記述し、そのファイルを実行して、コマンドを実行する必要がある場合があります。指定されたポリシー・ファイルには、コードに必要なアクセス権を付与するエントリが含まれているため、実行は、適切なアクセス権が付与されていないことを示す例外なく続行されるはずです。ユーザー名とパスワードの入力が求められ(
testUser
とtestPassword
を使用)、ログイン構成ファイルに指定されたSampleLoginModule
により、名前とパスワードがチェックされます。ログインが成功するとAuthentication succeeded!
というメッセージが表示され、失敗するとAuthentication failed:
の後に失敗の理由が続くメッセージが表示されます。 - 適切な
sampleacn.policy
/* grant the sample LoginModule permissions */
grant codebase "file:./SampleLM.jar" {
permission javax.security.auth.AuthPermission "modifyPrincipals";
};
grant codebase "file:./SampleAcn.jar" {
permission javax.security.auth.AuthPermission "createLoginContext.Sample";
};