JAAS認証チュートリアル

このチュートリアルでは、ユーザーの認証にJAASを使用する方法を説明します。現在実行しているユーザーを確実かつ安全に特定します。

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

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

  1. 認証チュートリアル・コード
  2. ログイン構成
  3. JAAS認証チュートリアル・コードの実行

チュートリアルのコードを最初に実行してみる場合は、「JAAS認証チュートリアル・コードの実行」に進んでから、他の項に戻り、コーディングおよび構成ファイルの詳細について学習してください。

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

このチュートリアルのコードは、3つのファイルで構成されます。

  • SampleAcn.javaには、サンプル・アプリケーション・クラス(SampleAcn)とユーザー入力の処理に使用される別のクラス(MyCallbackHandler)が含まれています。このチュートリアルでは、このファイル内のコードのみが理解する必要のあるコードです。アプリケーションでは、その他のソース・ファイルは間接的にのみ使用します。
  • SampleLoginModule.javaは、「JAAS認証チュートリアルのログイン構成ファイル」で説明されているチュートリアルのログイン構成ファイル(sample_jaas.config)によって、目的の基礎となる認証を実装するクラスとして指定されるクラスです。SampleLoginModuleのユーザー認証は、ユーザーによって指定された名前とパスワードが特定の値を持っていることを単に検証する処理です。
  • SamplePrincipal.javaは、java.security.Principalインタフェースを実装するサンプル・クラスです。SampleLoginModuleによって使用されます。

SampleAcn.java

認証チュートリアルのアプリケーション・コードは、単一のソース・ファイルSampleAcn.javaに含まれています。このファイルには次の2つのクラスが含まれています。

SampleAcnクラス

SampleAcnクラスのmainメソッドは、認証の実行後に、認証が成功したかどうかをレポートします。

ユーザーの認証用コードは、非常に簡潔です。次の2つのステップで構成されています。

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

ノート:

SampleAcnクラスでは、次のことも示しています:
  • Subject.callAs(Subject, Callable<T>)メソッドを使用して、指定したSubjectでコードを実行する方法
  • 構造化並行性を使用して、子スレッドから親スレッドの現在のSubjectにアクセスする方法。

詳細は、特定のSubjectとしてアクションを実行するためのcallAsおよび現在のメソッドを参照してください。

最初に基本的なコードを紹介します。続いて、必要な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_jaas.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.*;
import java.security.Principal;
import java.util.concurrent.Callable;
import java.util.concurrent.StructuredTaskScope;

/**
 * <p>This sample application attempts to authenticate a user and reports whether
 * or not the authentication was successful.</p>
 * 
 * <p>It also demonstrates how to run code with a specified Subject with the
 * Subject.callAs(Subject, Callable<T? method and how to access the parent
 * thread's current Subject from a child thread using structured concurrency and
 * the Subject.current() method.</p>
 */
public class SampleAcn {

   /**
    * <p>Attempt to authenticate the user.</p>
    *
    * @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);
        }

        // 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!");
        
        Subject mySubject = lc.getSubject();

        // Let's see what Principals we have
        Iterator principalIterator = mySubject.getPrincipals().iterator();
        System.out.println("Authenticated user has the following Principals:");
        
        while (principalIterator.hasNext()) {
            Principal p = (Principal)principalIterator.next();
            System.out.println("\t" + p.toString());
        }

        System.out.println("User has " +
                           mySubject.getPublicCredentials().size() +
                           " Public Credential(s)");
        
        Callable<Void> anotherAction = () -> {
            
            // Retrieve the current subject
            Subject s = Subject.current();
            System.out.println("\nCurrent subject: " + s);
            
            // Add a new Principal to the current subject
            Random r = new Random();
            String pn = Integer.toString(r.nextInt());
            Principal p = new sample.principal.SamplePrincipal(pn);
            
            System.out.println("\nAdding principal " + pn);
            s.getPrincipals().add(p);         
            
            // List the current subject's Principals
            System.out.println("\nAuthenticated user has the following Principals:");
            Iterator pi = s.getPrincipals().iterator();

            while (pi.hasNext()) {
                Principal nextp = (Principal)pi.next();
                System.out.println("\t" + nextp.toString());            
            }

            return null;            
        };        
        
        // Run the Callable anotherAction with the current subject
        
        Subject.callAs(mySubject, anotherAction);
        
        // The following demonstrates how to retrieve the parent thread's 
        // current subject from a child thread with structured concurrency.
        
        Callable<Void> addRandomPrincipal = () -> {
            
            Subject s = Subject.current();
            
            // Add a new Principal
            Random r = new Random();
            String pn = Integer.toString(r.nextInt());
            Principal p = new sample.principal.SamplePrincipal(pn);
            
            System.out.println("\nAdding principal " + pn);
            s.getPrincipals().add(p);         
            return null;
        };
        
        Callable<Void> structuredAction = () -> {
            try (var scope = new StructuredTaskScope<>()) {
                scope.fork(addRandomPrincipal);
                scope.fork(addRandomPrincipal);
                scope.fork(addRandomPrincipal);
                
                scope.join();
                
                Subject s = Subject.current();
                System.out.println("\nCurrent subject: " + s);
            
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        };        
        
        // Run the Callable anotherAction with the current subject
        
        Subject.callAs(mySubject, structuredAction);        
    }
}


/**
 * <p>The application implements the CallbackHandler.</p>
 *
 * <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.</p>
 */
 
class MyCallbackHandler implements CallbackHandler {

    /**
     * <p>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(readPassword(System.in));

            } else {
                throw new UnsupportedCallbackException
                        (callbacks[i], "Unrecognized Callback");
            }
        }
    }

    // Reads user password from given input stream.
    private char[] readPassword(InputStream in) throws IOException {

        char[] lineBuffer;
        char[] buf;
        int i;

        buf = lineBuffer = new char[128];

        int room = buf.length;
        int offset = 0;
        int c;

loop:   while (true) {
            switch (c = in.read()) {
            case -1:
            case '\n':
                break loop;

            case '\r':
                int c2 = in.read();
                if ((c2 != '\n') && (c2 != -1)) {
                    if (!(in instanceof PushbackInputStream)) {
                        in = new PushbackInputStream(in);
                    }
                    ((PushbackInputStream)in).unread(c2);
                } else
                    break loop;

            default:
                if (--room < 0) {
                    buf = new char[offset + 128];
                    room = buf.length - offset - 1;
                    System.arraycopy(lineBuffer, 0, buf, 0, offset);
                    Arrays.fill(lineBuffer, ' ');
                    lineBuffer = buf;
                }
                buf[offset++] = (char) c;
                break;
            }
        }

        if (offset == 0) {
            return null;
        }

        char[] ret = new char[offset];
        System.arraycopy(buf, 0, ret, 0, offset);
        Arrays.fill(buf, ' ');

        return ret;
    }
}
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の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_jaas.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認証チュートリアル・コードの実行

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 --enable-preview -source 24 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 --enable-preview -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 --enable-preview -Djava.security.auth.login.config==sample_jaas.config sample.SampleAcn
user name: testUser
password: testPassword
                [SampleLoginModule] user entered user name: testUser
                [SampleLoginModule] user entered password: testPassword
                [SampleLoginModule] authentication succeeded
                [SampleLoginModule] added SamplePrincipal to Subject
Authentication succeeded!
Authenticated user has the following Principals:
        SamplePrincipal:  testUser
User has 0 Public Credential(s)

Current subject: Subject:
        Principal: SamplePrincipal:  testUser


Adding principal -1501634219

Authenticated user has the following Principals:

Adding principal -1164915804

Adding principal 304653985

Adding principal -1764420412

Current subject: Subject:
        Principal: SamplePrincipal:  testUser
        Principal: SamplePrincipal:  -1501634219
        Principal: SamplePrincipal:  -1164915804
        Principal: SamplePrincipal:  304653985
        Principal: SamplePrincipal:  -1764420412