1. 概要

Java Authentication And Authorization Service (JAAS)は、Java SEの低レベルのセキュリティフレームワークであり、セキュリティモデルをコードベースのセキュリティからユーザーベースのセキュリティに拡張します。 JAASは、次の2つの目的で使用できます。

  • 認証:現在コードを実行しているエンティティを識別します
  • 承認:認証されたら、このエンティティが機密コードを実行するために必要なアクセス制御権限または権限を持っていることを確認します

このチュートリアルでは、さまざまなAPI、特に LoginModule を実装および構成することにより、サンプルアプリケーションでJAASをセットアップする方法について説明します。

2. JAASの仕組み

アプリケーションでJAASを使用する場合、いくつかのAPIが関係します。

  • CallbackHandler :ユーザーの資格情報を収集するために使用され、LoginContextの作成時にオプションで提供されます
  • 構成 LoginModule 実装のロードを担当し、LoginContextの作成時にオプションで提供できます
  • LoginModule :ユーザーの認証に効果的に使用されます

Configuration APIのデフォルトの実装を使用し、CallbackHandlerおよびLoginModuleAPIの独自の実装を提供します。

3. CallbackHandler実装の提供

LoginModule の実装を掘り下げる前に、まずユーザー資格情報の収集に使用されるCallbackHandlerインターフェースの実装を提供する必要があります

これには、コールバックの配列を受け入れる単一のメソッド handle()があります。 さらに、JAASはすでに多くの Callback 実装を提供しており、ユーザー名とパスワードを収集するためにそれぞれNameCallbackPasswordCallbackを使用します。

CallbackHandlerインターフェースの実装を見てみましょう。

public class ConsoleCallbackHandler implements CallbackHandler {

    @Override
    public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
        Console console = System.console();
        for (Callback callback : callbacks) {
            if (callback instanceof NameCallback) {
                NameCallback nameCallback = (NameCallback) callback;
                nameCallback.setName(console.readLine(nameCallback.getPrompt()));
            } else if (callback instanceof PasswordCallback) {
                PasswordCallback passwordCallback = (PasswordCallback) callback;
                passwordCallback.setPassword(console.readPassword(passwordCallback.getPrompt()));
            } else {
                throw new UnsupportedCallbackException(callback);
            }
        }
    }
}

したがって、ユーザー名を表示して読み取るために、次のものを使用しました。

NameCallback nameCallback = (NameCallback) callback;
nameCallback.setName(console.readLine(nameCallback.getPrompt()));

同様に、パスワードの入力を求めて読み取るには、次のようにします。

PasswordCallback passwordCallback = (PasswordCallback) callback;
passwordCallback.setPassword(console.readPassword(passwordCallback.getPrompt()));

後で、LoginModuleを実装するときにCallbackHandlerを呼び出す方法を説明します。

4. LoginModule実装の提供

簡単にするために、ハードコードされたユーザーを格納する実装を提供します。 それで、それを InMemoryLoginModuleと呼びましょう:

public class InMemoryLoginModule implements LoginModule {

    private static final String USERNAME = "testuser";
    private static final String PASSWORD = "testpassword";

    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map<String, ?> sharedState;
    private Map<String, ?> options;
    
    private boolean loginSucceeded = false;
    private Principal userPrincipal;
    //...
}

次のサブセクションでは、より重要なメソッド initialize() login()、および commit()の実装を示します。

4.1. initialize()

LoginModuleが最初にロードされ、次にSubjectとCallbackHandlerで初期化されます。 さらに、 LoginModule は、 Map を使用してデータを共有したり、別のMapを使用してプライベート構成データを保存したりできます。

public void initialize(
  Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
    this.sharedState = sharedState;
    this.options = options;
}

4.2. login()

login()メソッドでは、 NameCallbackPasswordCallbackを指定してCallbackHandler.handle()メソッドを呼び出し、プロンプトを表示して取得します。ユーザー名とパスワード。 次に、これらの提供された資格情報をハードコードされた資格情報と比較します。

@Override
public boolean login() throws LoginException {
    NameCallback nameCallback = new NameCallback("username: ");
    PasswordCallback passwordCallback = new PasswordCallback("password: ", false);
    try {
        callbackHandler.handle(new Callback[]{nameCallback, passwordCallback});
        String username = nameCallback.getName();
        String password = new String(passwordCallback.getPassword());
        if (USERNAME.equals(username) && PASSWORD.equals(password)) {
            loginSucceeded = true;
        }
    } catch (IOException | UnsupportedCallbackException e) {
        //...
    }
    return loginSucceeded;
}

login()メソッドは、操作が成功した場合はtrueを返し、ログインが失敗した場合はfalseを返す必要があります

4.3. commit()

LoginModule#login へのすべての呼び出しが成功した場合、追加のプリンシパルでサブジェクトを更新します。

@Override
public boolean commit() throws LoginException {
    if (!loginSucceeded) {
        return false;
    }
    userPrincipal = new UserPrincipal(username);
    subject.getPrincipals().add(userPrincipal);
    return true;
}

それ以外の場合は、 abort()メソッドが呼び出されます。

この時点で、 LoginModule 実装の準備ができており、Configurationサービスプロバイダーを使用して動的にロードできるように構成する必要があります。

5. LoginModule構成

JAASは、 Configuration サービスプロバイダーを使用して、実行時にLoginModuleをロードします。 デフォルトでは、 ConfigFile 実装を提供して使用します。この実装では、LoginModuleがログインファイルを介して構成されます。 たとえば、LoginModuleに使用されるファイルの内容は次のとおりです。

jaasApplication {
   com.baeldung.jaas.loginmodule.InMemoryLoginModule required debug=true;
};

ご覧のとおり、 LoginModule実装の完全修飾クラス名 required フラグ、およびデバッグ用のオプションを提供しています。

最後に、java.security.auth.login.configシステムプロパティを使用してログインファイルを指定することもできることに注意してください。

$ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config

また、Javaセキュリティファイル ${java.home}/jre/lib/security/java.securityのプロパティlogin.config.urlを介して1つ以上のログインファイルを指定することもできます。 ]:

login.config.url.1=file:${user.home}/.java.login.config

6. 認証

まず、アプリケーションは、LoginContextインスタンスを作成して認証プロセスを初期化します。 そのためには、完全なコンストラクターを調べて、パラメーターとして必要なものについて理解することができます。

LoginContext(String name, Subject subject, CallbackHandler callbackHandler, Configuration config)
  • name :対応するLoginModuleのみをロードするためのインデックスとして使用されます
  • subject :ログインしたいユーザーまたはサービスを表します
  • callbackHandler :アプリケーションからLoginModuleにユーザー資格情報を渡す責任があります
  • config :nameパラメーターに対応するLoginModuleのロードを担当します

ここでは、オーバーロードされたコンストラクターを使用して、CallbackHandler実装を提供します。

LoginContext(String name, CallbackHandler callbackHandler)

CallbackHandlerと構成済みのLoginModuleができたので、 LoginContextオブジェクトを初期化することで、認証プロセスを開始できます。

LoginContext loginContext = new LoginContext("jaasApplication", new ConsoleCallbackHandler());

この時点で、 login()メソッドを呼び出してユーザーを認証できます

loginContext.login();

次に、 login()メソッドは、 LoginModule の新しいインスタンスを作成し、その login()メソッドを呼び出します。 そして、認証が成功すると、認証されたサブジェクトを取得できます。

Subject subject = loginContext.getSubject();

次に、LoginModuleが配線されているサンプルアプリケーションを実行してみましょう。

$ mvn clean package
$ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config \
    -classpath target/core-java-security-2-0.1.0-SNAPSHOT.jar com.baeldung.jaas.JaasAuthentication

ユーザー名とパスワードの入力を求められたら、testusertestpasswordを資格情報として使用します。

7. 承認

承認は、ユーザーが最初に接続され、AccessControlContextに関連付けられたときに機能します。 Javaセキュリティポリシーを使用して、プリンシパルに1つ以上のアクセス制御権限を付与できます。 次に、SecurityManager#checkPermissionメソッドを呼び出すことで、機密性の高いコードへのアクセスを防ぐことができます。

SecurityManager.checkPermission(Permission perm)

7.1. 権限の定義

アクセス制御権または許可は、リソースに対してアクションを実行する機能です。 Permission 抽象クラスをサブクラス化することにより、パーミッションを実装できます。 そのためには、リソース名と可能なアクションのセットを提供する必要があります。 たとえば、 FilePermission を使用して、ファイルのアクセス制御権限を構成できます。 可能なアクションは、読み取り書き込み実行などです。 アクションが不要なシナリオでは、BasicPermisionを使用するだけです。

次に、 ResourcePermission クラスを介してアクセス許可の実装を提供します。このクラスでは、ユーザーがリソースにアクセスするためのアクセス許可を持っている可能性があります。

public final class ResourcePermission extends BasicPermission {
    public ResourcePermission(String name) {
        super(name);
    }
}

後で、Javaセキュリティポリシーを使用してこのアクセス許可のエントリを構成します。

7.2. 権限の付与

通常、ポリシーツールを使用して作成できるため、ポリシーファイルの構文を知る必要はありません。 ポリシーファイルを見てみましょう。

grant principal com.sun.security.auth.UserPrincipal testuser {
    permission com.baeldung.jaas.ResourcePermission "test_resource"
};

このサンプルでは、test_resource権限をtestuserユーザーに付与しています。

7.3. 権限の確認

Subject が認証され、権限が構成されると、 Subject#doAsまたはSubject#doAsPrivilieged静的メソッドを呼び出してアクセスを確認できます。 この目的のために、機密コードへのアクセスを保護できるPrivilegedActionを提供します。 run()メソッドでは、 SecurityManager#checkPermission メソッドを呼び出して、認証されたユーザーがtest_resource権限を持っていることを確認します。

public class ResourceAction implements PrivilegedAction {
    @Override
    public Object run() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new ResourcePermission("test_resource"));
        }
        System.out.println("I have access to test_resource !");
        return null;
    }
}

最後に、 Subject#doAsPrivilegedメソッドを呼び出します。

Subject subject = loginContext.getSubject();
PrivilegedAction privilegedAction = new ResourceAction();
Subject.doAsPrivileged(subject, privilegedAction, null);

認証と同様に、承認用の単純なアプリケーションを実行します。ここでは、 LoginModule に加えて、アクセス許可構成ファイルを提供します。

$ mvn clean package
$ java -Djava.security.manager -Djava.security.policy=src/main/resources/jaas/jaas.policy \
    -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config \
    -classpath target/core-java-security-2-0.1.0-SNAPSHOT.jar com.baeldung.jaas.JaasAuthorization

8. 結論

この記事では、主要なクラスとインターフェースを調べ、それらを構成する方法を示すことにより、JAASを実装する方法を紹介しました。 特に、サービスプロバイダーLoginModuleを実装しました。

いつものように、この記事のコードはGitHubから入手できます。