プライマリ・コンテンツに移動
Java Platform, Standard Editionセキュリティ開発者ガイド
リリース10
E94999-01
目次へ移動
目次

前
次
次へ

JAAS認証チュートリアル

JAASは、次の2つの目的で使用できます。

このセクションでは、認証コンポーネントの基本的なチュートリアルを提供します。承認コンポーネントについては、「JAAS承認」チュートリアルで説明します。

JAAS認証は、プラガブルな方式で実行されます。つまり、Javaアプリケーションは、基盤となる認証技術から独立して機能します。新規または更新された技術を、アプリケーション自体を変更せずに、プラグインとして使用可能です。使用する特定の認証技術の実装は実行時に決定されます。その内容は、ログイン構成ファイルに指定します。このチュートリアルで使用する認証技術は、ユーザーが特定の名前とパスワードを指定しているかどうかを確認するだけの非常に基本的なものです。

このチュートリアルは、次のセクションで構成されます。

  1. 認証チュートリアル・コード
  2. ログイン構成
  3. コードの実行
  4. セキュリティ・マネージャを使用したコードの実行

チュートリアルのコードを最初に実行してみる場合は、「コードの実行」を先に読んでから、その他のセクションに戻り、コードの記述および構成ファイルの詳細について学習してください。

認証チュートリアル・コード

このチュートリアルのコードは、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つのステップで構成されています。

  1. LoginContextのインスタンス化
  2. LoginContextのloginメソッドの呼び出し

最初に基本的なコードを紹介します。続いて、必要な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());

引数について、次に説明します。

  1. JAASログイン構成ファイルのエントリ名

    これは、LoginContextが、ログイン構成に説明されているJAASログイン構成ファイル内でこのアプリケーションのエントリ検索に使用する名前です。このようなエントリは、基盤となる適切な認証テクノロジを実装するクラスを指定します。クラスは、javax.security.auth.spiパッケージ内のLoginModuleインタフェースを実装する必要があります。

    このサンプル・コードでは、このチュートリアルに付属しているSampleLoginModuleを使用します。SampleLoginModuleは、ユーザーが特定の名前およびパスワードを入力したかどうかを確認することによって認証を実行します。

    このチュートリアルで使用するログイン構成ファイル(sample_jass.config)のエントリ(JAAS認証チュートリアルのログイン構成ファイルを参照)は、「Sample」という名前です。LoginContextコンストラクタの最初の引数には、この名前を指定してください。

  2. 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を使用してインスタンス化します。

次に、LoginContextloginメソッドによってSampleLoginModule内のメソッドが呼び出され、ログインおよび認証が実行されます。SampleLoginModuleは、MyCallbackHandlerを使用してユーザー名とパスワードを取得します。次に、SampleLoginModuleは名前とパスワードが適切であるかどうかをチェックします。

認証に成功した場合、SampleLoginModuleは、ユーザーを表すプリンシパルサブジェクトに追加します。SampleLoginModuleによってサブジェクトに追加されるプリンシパルは、SamplePrincipal (java.security.Principalインタフェースを実装するサンプル・クラス)のインスタンスです。

その後、呼出し側アプリケーションは、LoginContextgetSubjectメソッドを呼び出し(このステップは、このチュートリアルではオプション)、認証されたサブジェクトを取得します。

完全な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内に適切な値を設定します。

MyCallbackHandlerhandleメソッドの構築方法は次のとおりです。

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");
    }
  }
}

CallbackHandlerhandleメソッドは、特定の型のCallbackインスタンス(NameCallbackPasswordCallbackなど)の配列を受け取ります。このメソッドは、アプリケーションの実行に適した方法でユーザーと通信し、各Callbackを処理します。

MyCallbackHandlerは、3つのタイプのコールバック(ユーザーにユーザー名の入力を求めるNameCallback、パスワードの入力を求めるPasswordCallbackSampleLoginModuleからユーザーに送信するメッセージ(エラー、警告など)を報告するTextOutputCallback)を処理します。

handleメソッドは、報告するメッセージを抽出し、System.outに出力することにより、TextOutputCallbackを処理します。オプションで、追加の文字列(メッセージ・タイプによって異なる)が前に付けられます。報告されるメッセージは、TextOutputCallbackgetMessageメソッドを呼び出すことによって決定され、タイプは、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にプロンプトを出力します。次に、NameCallbacksetNameメソッドを呼び出し、ユーザーによって入力された名前を渡すことにより、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にパスワードの入力を求めるプロンプトを出力します。次に、PasswordCallbacksetPasswordメソッドを呼び出し、ユーザーによって入力されたパスワードを渡すことにより、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.javaLoginModuleインタフェースを実装します。SampleLoginModuleは、チュートリアルのログイン構成ファイル(JAAS認証チュートリアルのログイン構成ファイルを参照)によって、目的の基礎となる認証を実装するクラスとして指定されるクラスです。SampleLoginModuleのユーザー認証は、ユーザーによって指定された名前とパスワードが特定の値を持っていることを単に検証する処理です。このSampleLoginModuleは、チュートリアルのログイン構成ファイルにより、LoginModuleとして指定されます。これは、(1)あらゆる環境に適切な基本タイプの認証を実行するため、すべてのユーザーのチュートリアルで使用でき、(2)認証技術を実装するLoginModuleを記述できる必要がある上級プログラマ向けのLoginModuleの実装例を提供するからです。

SamplePrincipal.javaは、java.security.Principalインタフェースを実装するサンプル・クラスです。認証に成功した場合、SampleLoginModuleは、ユーザーを表すSamplePrincipalサブジェクトに追加します。

重要: アプリケーションの作成者は、LoginModuleプリンシパル実装の記述方法を理解していなくてもかまいません。SampleLoginModuleSamplePrincipalのコードを確認する必要はありません。アプリケーションの作成と構成情報(ログイン構成ファイルの内容など)の指定に集中し、アプリケーションが構成によって指定されたLoginModuleを利用してユーザーを認証できるようにしてください。使用するLoginModuleを決定し、LoginModuleのドキュメントからLoginModuleの動作を制御するために(構成内に)値を指定できるオプションを確認してください。

どのベンダーの提供するログイン・モジュール実装でも使用可能です。付録B: JAASログイン構成ファイルに記載されているように、いくつかの実装がOracleのJREに同梱されています。

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がエントリを参照するために、使用する名前です。このエントリは、ユーザー認証の実行に使用するLoginModulesample.moduleパッケージ内のSampleLoginModuleであること、および認証が成功したと見なされるためにはこのSampleLoginModuleが「成功する」必要があることを示します。SampleLoginModuleは、ユーザーから提供された名前とパスワードが期待したもの(それぞれ「testUser」と「testPassword」)である場合にかぎり成功します。

SampleLoginModuleは「debug」オプションも定義します(trueに設定可能)。このオプションの値をtrueに設定すると、SampleLoginModuleにより、認証の進捗に関する追加情報が出力されます。LoginModuleで定義できるオプションの数に制限はありません。LoginModuleのドキュメントには、構成ファイル内に設定可能なオプションの名前と値が記載されています。

コードの実行

JAAS認証チュートリアル・コードを実行するには、次の操作を行う必要があります。

  1. 次のファイルを1つのディレクトリ内に格納します。

  2. 最上位ディレクトリの下にsampleという名前のサブディレクトリを作成し、ここに次のものを格納します(注: SampleAcn.java内のSampleAcnおよびMyCallbackHandlerクラスはsampleパッケージ内にある)。

  3. sampleディレクトリのサブディレクトリを作成し、moduleという名前を付けます。ここに次のものを格納します(注: SampleLoginModuleクラスはsample.moduleパッケージ内にある)。

  4. sampleディレクトリのサブディレクトリをもう1つ作成し、principalという名前を付けます。ここに次のものを格納します(注: SamplePrincipalクラスはsample.principalパッケージ内にある)。
  5. 最上位のディレクトリで、SampleAcn.javaSampleLoginModule.javaSamplePrincipal.javaをコンパイルします。

    javac sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java

    コマンド全体を1行に入力してください。

  6. 次を指定して、SampleAcnアプリケーションを実行します
    • -Djava.security.auth.login.config==sample_jaas.config。使用するログイン構成ファイルとしてsample_jaas.configを指定します。

次に、完全なコマンドを示します。

java -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn

ユーザー名とパスワードの入力が求められ、ログイン構成ファイルに指定されたSampleLoginModuleにより、これらが正しいことが確認されます。SampleLoginModuleは、ユーザー名にtestUser、パスワードにtestPasswordを期待します。

debugオプション(ログイン構成ファイル内でtrueに設定)の結果として、SampleLoginModuleによりメッセージが出力されます。ログインに成功すると、SampleAcnにより、次のメッセージが表示されます。

Authentication succeeded!

ログインに失敗した場合(パスワードのスペル・ミスなどにより)、次のメッセージが表示されます。

Authentication failed:

このあとに、失敗の原因が示されます。たとえば、パスワードのスペル・ミスがあった場合、次のようなメッセージが表示されます。

Authentication failed:
  Password Incorrect

SampleAcnのログインの最大試行回数は3回です。

セキュリティ・マネージャを使用したコードの実行

セキュリティ・マネージャがインストールされた環境でJavaプログラムを実行する場合、有効なセキュリティ・ポリシーによりアクセス権が明示的に付与されているのでないかぎり、リソースへのアクセスやセキュリティ関連操作の実行は許可されません。(JDKでのアクセス権を参照。)アクセス権をポリシー・ファイル内のエントリによって付与する必要があります(デフォルトのPolicyの実装とポリシー・ファイルの構文を参照。)

大半のブラウザはセキュリティ・マネージャをインストールするため、一般にアプレットは、セキュリティ・マネージャの監視下で実行されます。一方、アプリケーションでは、アプリケーションの実行時にセキュリティ・マネージャは自動的にインストールされないため、そのようなことはありません。このため、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. 次のファイルを1つのディレクトリ内に格納します。

  2. 最上位ディレクトリの下にsampleという名前のサブディレクトリを作成し、ここに次のものを格納します(注: SampleAcn.java内のSampleAcnおよびMyCallbackHandlerクラスはsampleパッケージ内にある)。

  3. sampleディレクトリのサブディレクトリを作成し、moduleという名前を付けます。ここに次のものを格納します(注: SampleLoginModuleクラスはsample.moduleパッケージ内にある)。

  4. sampleディレクトリのサブディレクトリをもう1つ作成し、principalという名前を付けます。ここに次のものを格納します(注: SamplePrincipalクラスはsample.principalパッケージ内にある)。

  5. 最上位のディレクトリで、SampleAcn.javaSampleLoginModule.javaSamplePrincipal.javaをコンパイルします。

    javac sample/SampleAcn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java

    コマンド全体を1行に入力してください。

  6. SampleAcn.classMyCallbackHandler.classを含むJARファイルを作成します。

    jar -cvf SampleAcn.jar sample/SampleAcn.class sample/MyCallbackHandler.class

    コマンド全体を1行に入力してください。このコマンドにより、JARファイルSampleAcn.jarが作成され、その内部にSampleAcn.classMyCallbackHandler.classが格納されます。

  7. SampleLoginModule.classSamplePrincipal.classを含むJARファイルを作成します。

    jar -cvf SampleLM.jar sample/module/SampleLoginModule.class sample/principal/SamplePrincipal.class

  8. 適切なアクセス権を付与するポリシー・ファイルを作成します。

    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アプリケーションを実行します

    1. 適切な-classpath節(SampleAcn.jarおよびSampleLM.jar JARファイル内のクラスを検索するため)。
    2. -Djava.security.manager。セキュリティ・マネージャのインストールを指定します。
    3. -Djava.security.policy==sampleacn.policy。使用するポリシー・ファイルとしてsampleacn.policyを指定します。
    4. -Djava.security.auth.login.config==sample_jaas.config。使用するログイン構成ファイルとしてsample_jaas.configを指定します。

    次に、WindowsおよびSolaris、LinuxおよびmacOSシステムで使用するすべてのコマンドを示します。クラス・パス項目の区切りとして、Solaris、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
    

    次に、Solaris、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ファイル(Microsoft Windowsの場合)または.shファイル(Solaris、LinuxおよびmacOSの場合)に記述し、そのファイルを実行して、コマンドを実行する必要がある場合があります。

    指定されたポリシー・ファイルには、コードに必要なアクセス権を付与するエントリが含まれているため、実行は、適切なアクセス権が付与されていないことを示す例外なく続行されるはずです。ユーザー名とパスワードの入力が求められ(testUsertestPasswordを使用)、ログイン構成ファイルに指定された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";
};