Java EE 8セキュリティAPI

1.概要

Java EE 8セキュリティAPIは、Javaコンテナのセキュリティ上の問題を処理するための新しい標準で移植性の高い方法です。

この記事では、APIの3つの主要機能について説明します。

  1. HTTP認証メカニズム

  2. アイデンティティストア

  3. セキュリティコンテキスト

最初に、提供されている実装を構成する方法を理解し、次にカスタムの実装を実装する方法を理解します。

2. Mavenの依存関係

Java EE 8セキュリティAPIを設定するには、サーバー提供の実装または明示的な実装が必要です。

2.1. サーバー実装の使用

Java EE 8準拠サーバーはすでにJava EE 8セキュリティーAPIの実装を提供しているので、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22javax%22% 20AND%20a%3A%22javaee-web-api%22[Java EE WebプロファイルAPI]Mavenアーティファクト:

<dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-web-api</artifactId>
        <version>8.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

2.2. 明示的な実装を使用する

まず、Java EE 8のMavenアーティファクトを指定します[ セキュリティAPI ]。

<dependencies>
    <dependency>
        <groupId>javax.security.enterprise</groupId>
        <artifactId>javax.security.enterprise-api</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

そして、例えばhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22javax.security.enterprise%22[Soteria]という参照実装を実装します。 :

<dependencies>
    <dependency>
        <groupId>org.glassfish.soteria</groupId>
        <artifactId>javax.security.enterprise</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>

3. HTTP認証メカニズム

Java EE 8より前のバージョンでは、 web.xml ファイルを介して宣言的に認証メカニズムを設定していました。

このバージョンでは、Java EE 8セキュリティAPIが新しい HttpAuthenticationMechanism インタフェースを代替として設計しました。したがって、Webアプリケーションはこのインタフェースの実装を提供することによって認証メカニズムを設定できるようになりました。

幸い、コンテナはサーブレット仕様で定義されている3つの認証方法(基本HTTP認証、フォームベース認証、およびカスタムフォームベース認証)それぞれの実装をすでに提供しています。

また、各実装を起動するための注釈も用意されています。

  1. @ BasicAuthenticationMechanismDefinition

  2. @ FormAuthenticationMechanismDefinition

  3. @ CustomFormAuthenrticationMechanismDefinition

3.1. 基本HTTP認証

  • 上記のように、WebアプリケーションはCDI Beanの__ @ BasicAuthenticationMechanismDefinitionアノテーションを使用するだけで基本HTTP認証を設定できます。

@BasicAuthenticationMechanismDefinition(
  realmName = "userRealm")
@ApplicationScoped
public class AppConfig{}

この時点で、サーブレットコンテナは HttpAuthenticationMechanism インタフェースの提供された実装を検索してインスタンス化します。

不正な要求を受信すると、コンテナは WWW-Authenticate 応答ヘッダーを介して適切な認証情報を提供するようにクライアントに要求します。

WWW-Authenticate: Basic realm="userRealm"

次にクライアントは、コロン「:」で区切られ、Base64でエンコードされた Authorization 要求ヘッダーを介してユーザー名とパスワードを送信します。

----//user=baeldung, password=baeldung
Authorization: Basic YmFlbGR1bmc6YmFlbGR1bmc=
----

資格情報を提供するために表示されるダイアログは、サーバーからではなくブラウザからのものであることに注意してください。

3.2. フォームベースのHTTP認証

  • @ FormAuthenticationMechanismDefinition アノテーションは、サーブレット仕様で定義されているように** フォームベースの認証をトリガします。

次に、ログインページとエラーページを指定するか、デフォルトの妥当なページ /login /login-error を使用するかを選択できます。

@FormAuthenticationMechanismDefinition(
  loginToContinue = @LoginToContinue(
    loginPage = "/login.html",
    errorPage = "/login-error.html"))
@ApplicationScoped
public class AppConfig{}

__loginPageを呼び出した結果、サーバーはフォームをクライアントに送信する必要があります。

<form action="j__security__check" method="post">
    <input name="j__username" type="text"/>
    <input name="j__password" type="password"/>
    <input type="submit">
</form>

クライアントはコンテナから提供された定義済みのバッキング認証プロセスにフォームを送信する必要があります。

3.3. カスタムフォームベースのHTTP認証

Webアプリケーションは、アノテーション__ @ CustomFormAuthenticationMechanismDefinitionを使用して、カスタムフォームベースの認証実装をトリガできます。

@CustomFormAuthenticationMechanismDefinition(
  loginToContinue = @LoginToContinue(loginPage = "/login.xhtml"))
@ApplicationScoped
public class AppConfig {
}

しかし、デフォルトのフォームベース認証とは異なり、カスタムログインページを設定し、 SecurityContext.authenticate() メソッドを補助認証プロセスとして呼び出します。

ログインロジックを含むバッキング LoginBean も見てみましょう。

@Named
@RequestScoped
public class LoginBean {

    @Inject
    private SecurityContext securityContext;

    @NotNull private String username;

    @NotNull private String password;

    public void login() {
        Credential credential = new UsernamePasswordCredential(
          username, new Password(password));
        AuthenticationStatus status = securityContext
          .authenticate(
            getHttpRequestFromFacesContext(),
            getHttpResponseFromFacesContext(),
            withParams().credential(credential));
       //...
    }

   //...
}

カスタム login.xhtml ページを呼び出した結果、クライアントは受信したフォームを __LoginBeanの login()__メソッドに送信します。

----//...
<input type="submit" value="Login" jsf:action="#{loginBean.login}"/>
----

3.4. カスタム認証メカニズム

HttpAuthenticationMechanism インターフェースは3つのメソッドを定義します。 ** 最も重要なのは__validateRequest()です。

他の2つのメソッド、 secureResponse() および cleanSubject() のデフォルトの動作でほとんどの場合は十分です。

実装例を見てみましょう。

@ApplicationScoped
public class CustomAuthentication
  implements HttpAuthenticationMechanism {

    @Override
    public AuthenticationStatus validateRequest(
      HttpServletRequest request,
      HttpServletResponse response,
      HttpMessageContext httpMsgContext)
      throws AuthenticationException {

        String username = request.getParameter("username");
        String password = response.getParameter("password");
       //mocking UserDetail, but in real life, we can obtain it from a database
        UserDetail userDetail = findByUserNameAndPassword(username, password);
        if (userDetail != null) {
            return httpMsgContext.notifyContainerAboutLogin(
              new CustomPrincipal(userDetail),
              new HashSet<>(userDetail.getRoles()));
        }
        return httpMsgContext.responseUnauthorized();
    }
   //...
}

ここでは、実装が検証プロセスのビジネスロジックを提供しますが、実際には validate を呼び出す IdentityStoreHandler b __y を介して IdentityStore__に委任することをお勧めします。

CDI対応にする必要があるため、実装には @ ApplicationScoped アノテーションも付けています。

信任状の有効な検証、およびユーザー役割の最終的な検索の後、実装はコンテナーに通知します。

HttpMessageContext.notifyContainerAboutLogin(Principal principal, Set groups)

3.5. サーブレットセキュリティの強化

  • Webアプリケーションはサーブレット実装で@ServletSecurity__アノテーションを使用することでセキュリティ制約を強制することができます。

@WebServlet("/secured")
@ServletSecurity(
  value = @HttpConstraint(rolesAllowed = {"admin__role"}),
  httpMethodConstraints = {
    @HttpMethodConstraint(
      value = "GET",
      rolesAllowed = {"user__role"}),
    @HttpMethodConstraint(
      value = "POST",
      rolesAllowed = {"admin__role"})
  })
public class SecuredServlet extends HttpServlet {
}

このアノテーションは httpMethodConstraints value の2つの属性を持ちます。 httpMethodConstraints は、1つ以上の制約を指定するために使用されます。各制約は、許可されたロールのリストによるHTTPメソッドへのアクセス制御を表します。

接続されたユーザーがリソースにアクセスするための適切な役割を持っているかどうか、コンテナはすべての url-pattern およびHTTPメソッドについてチェックします。

4.アイデンティティストア

この機能は、 IdentityStore インターフェイスによって抽象化されており、資格情報を検証し、最終的にグループメンバーシップを取得するために使用されます。 言い換えれば、それは認証、許可、あるいはその両方の機能を提供することができます。

IdentityStore は、呼び出された IdentityStoreHandler インターフェースを介して HttpAuthenticationMecanism によって使用されることを意図しており、推奨されています。 IdentityStoreHandler のデフォルト実装は、Servlet __ __containerによって提供されます。

アプリケーションは、 IdentityStore の実装を提供することも、DatabaseとLDAPのコンテナによって提供される2つの組み込み実装のうちの1つを使用することもできます。

4.1. 組み込みIDストア

Java EE準拠サーバーは、2つのIDストア(データベースとLDAP)の実装を提供する必要があります。

  • データベース IdentityStore 実装は、設定データを @ DataBaseIdentityStoreDefinition アノテーションに渡すことによって初期化されます。

@DatabaseIdentityStoreDefinition(
  dataSourceLookup = "java:comp/env/jdbc/securityDS",
  callerQuery = "select password from users where username = ?",
  groupsQuery = "select GROUPNAME from groups where username = ?",
  priority=30)
@ApplicationScoped
public class AppConfig {
}

設定データとして、 外部データベースへのJNDIデータソース 、呼び出し元とそのグループをチェックするための2つのJDBCステートメント、そして最後に倍数ストアの場合に使用される優先度パラメータが設定されます。

優先度の高い IdentityStore は、後で__IdentityStoreHandlerによって処理されます。

データベースと同様に、 LDAP IdentityStore実装は、設定データを渡すことによって @ LdapIdentityStoreDefinition を通じて初期化されます。

@LdapIdentityStoreDefinition(
  url = "ldap://localhost:10389",
  callerBaseDn = "ou=caller,dc=baeldung,dc=com",
  groupSearchBase = "ou=group,dc=baeldung,dc=com",
  groupSearchFilter = "(&(member=%s)(objectClass=groupOfNames))")
@ApplicationScoped
public class AppConfig {
}

ここでは、外部LDAPサーバーのURL、LDAPディレクトリで発信者を検索する方法、および彼のグループを取得する方法が必要です。

4.2. カスタム IdentityStore を実装する

  • IdentityStore インタフェースは4つのデフォルトメソッドを定義します。

default CredentialValidationResult validate(
  Credential credential)
default Set<String> getCallerGroups(
  CredentialValidationResult validationResult)
default int priority()
default Set<ValidationType> validationTypes()

__priority() methodは、この実装が IdentityStoreHandlerによって処理される反復順序の値を返します。優先順位の低い IdentityStore が最初に処理されます。

デフォルトでは、 IdentityStore は、資格情報の検証 (ValidationType.VALIDATE) とグループの取得( ValidationType.PROVIDE GROUPS__)の両方を処理します。この動作をオーバーライドして、1つの機能しか提供できないようにすることができます。

したがって、 IdentityStore を認証情報の検証にのみ使用するように設定できます。

@Override
public Set<ValidationType> validationTypes() {
    return EnumSet.of(ValidationType.VALIDATE);
}

この場合、 validate() メソッドの実装を提供する必要があります。

@ApplicationScoped
public class InMemoryIdentityStore implements IdentityStore {
   //init from a file or harcoded
    private Map<String, UserDetails> users = new HashMap<>();

    @Override
    public int priority() {
        return 70;
    }

    @Override
    public Set<ValidationType> validationTypes() {
        return EnumSet.of(ValidationType.VALIDATE);
    }

    public CredentialValidationResult validate(
      UsernamePasswordCredential credential) {

        UserDetails user = users.get(credential.getCaller());
        if (credential.compareTo(user.getLogin(), user.getPassword())) {
            return new CredentialValidationResult(user.getLogin());
        }
        return INVALID__RESULT;
    }
}

または、グループの取得にのみ使用できるように IdentityStore を構成することを選択できます。

@Override
public Set<ValidationType> validationTypes() {
    return EnumSet.of(ValidationType.PROVIDE__GROUPS);
}

それから getCallerGroups() メソッドの実装を提供します。

@ApplicationScoped
public class InMemoryIdentityStore implements IdentityStore {
   //init from a file or harcoded
    private Map<String, UserDetails> users = new HashMap<>();

    @Override
    public int priority() {
        return 90;
    }

    @Override
    public Set<ValidationType> validationTypes() {
        return EnumSet.of(ValidationType.PROVIDE__GROUPS);
    }

    @Override
    public Set<String> getCallerGroups(CredentialValidationResult validationResult) {
        UserDetails user = users.get(
          validationResult.getCallerPrincipal().getName());
        return new HashSet<>(user.getRoles());
    }
}

IdentityStoreHandler は実装がCDI Beanであることを期待しているので、それを ApplicationScoped アノテーションで装飾します。

5.セキュリティコンテキストAPI

Java EE 8セキュリティAPIは、 SecurityContext インタフェースを介してプログラムによるセキュリティへのアクセスポイントを提供します。コンテナによって強制されている宣言型セキュリティモデルでは十分でない場合、これは代替手段です。

SecurityContext インタフェースのデフォルト実装は、実行時にCDI Beanとして提供されるべきです。したがって、それをインジェクトする必要があります。

@Inject
SecurityContext securityContext;

この時点で、ユーザーを認証し、認証されたユーザーを取得し、そのロールのメンバーシップを確認し、5つの利用可能な方法でWebリソースへのアクセスを許可または拒否できます。

5.1. 発信者データの取得

以前のバージョンのJava EEでは、 Principal を取得するか、ロールのメンバーシップをコンテナごとに確認していました。

サーブレットコンテナでは HttpServletRequest getUserPrincipal() および isUserInRole() メソッドを使用していますが、EJBコンテナでは同様のメソッド getCallerPrincipal()および isCallerInRole()のメソッド __を使用しています。

  • 新しいJava EE 8セキュリティAPIは、 SecurityContext インタフェースを通じて同様のメソッドを提供することによってこれを標準化しました。

Principal getCallerPrincipal();
boolean isCallerInRole(String role);
<T extends Principal> Set<T> getPrincipalsByType(Class<T> type);

getCallerPrincipal() メソッドは認証された呼び出し元のコンテナ固有の表現を返し、 getPrincipalsByType() メソッドは指定されたタイプのすべてのプリンシパルを取得します。

アプリケーション固有の呼び出し元がコンテナの呼び出し元と異なる場合に役立ちます。

5.2. Webリソースアクセスのテスト

まず、保護されたリソースを設定する必要があります。

@WebServlet("/protectedServlet")
@ServletSecurity(@HttpConstraint(rolesAllowed = "USER__ROLE"))
public class ProtectedServlet extends HttpServlet {
   //...
}

そして、この保護されたリソースへのアクセスを確認するには、__hasAccessToWebResource()メソッドを呼び出します。

securityContext.hasAccessToWebResource("/protectedServlet", "GET");

この場合、ユーザがロール USER ROLEに属していれば、メソッドはtrueを返します。

5.3. プログラムによる発信者の認証

アプリケーションは authenticate() を呼び出すことによってプログラムで認証プロセスを起動できます。

AuthenticationStatus authenticate(
  HttpServletRequest request,
  HttpServletResponse response,
  AuthenticationParameters parameters);

その後、コンテナは通知を受け、次にアプリケーション用に設定された認証メカニズムを起動します。 AuthenticationParameters パラメーターは、__HttpAuthenticationMechanismに対する信任状を提供します。

withParams().credential(credential)

AuthenticationStatus SUCCESS SEND FAILURE の値は、成功した認証と失敗した認証を設計し、 SEND CONTINUE は認証プロセスの進行中のステータスを通知します。

6.例題の実行

これらの例を強調するために、Java EE 8をサポートするhttps://openliberty.io/[Open Liberty]Serverの最新の開発ビルドを使用しました。これはhttps://search.mavenのおかげでダウンロードおよびインストールされます。 org/classic/#search%7Cga%7C1%7Ca%3A%22liberty-maven-plugin%22[liberty-maven-plugin]これでアプリケーションをデプロイしてサーバーを起動することもできます。

例を実行するには、対応するモジュールにアクセスして次のコマンドを呼び出すだけです。

mvn clean package liberty:run

その結果、Mavenはサーバーをダウンロードし、アプリケーションをビルド、デプロイ、および実行します。

7.まとめ

この記事では、新しいJava EE 8セキュリティーAPIの主な機能の構成と実装について説明しました。

まず、デフォルトの組み込み認証メカニズムの設定方法とカスタムの認証メカニズムの実装方法を示すことから始めました。後で、組み込みのIDストアを構成する方法とカスタムのものを実装する方法を見ました。そして最後に、__SecurityContextのメソッドを呼び出す方法を見ました。

いつものように、この記事のコード例はhttps://github.com/eugenp/tutorials/tree/master/java-ee-8-security-api[GitHubで利用可能]です。

前の投稿:Injektを使ったKotlinへの依存性注入
次の投稿:JavaのAIライブラリの概要