Java EE 8セキュリティAPI

Java EE 8セキュリティAPI

1. 概要

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

この記事では、we’ll look at the three core features of the API:

  1. HTTP認証メカニズム

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

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

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

2. Mavenの依存関係

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

2.1. サーバー実装の使用

Java EE8準拠のサーバーはすでにJavaEE 8 Security APIの実装を提供しているため、必要なのはJava EE Web Profile APIMavenアーティファクトのみです。


    
        javax
        javaee-web-api
        8.0
        provided
    

2.2. 明示的な実装の使用

まず、Java EE 8Security APIのMavenアーティファクトを指定します。


    
        javax.security.enterprise
        javax.security.enterprise-api
        1.0
    

次に、実装を追加します。たとえば、Soteria - リファレンス実装です。


    
        org.glassfish.soteria
        javax.security.enterprise
        1.0
    

3. HTTP認証メカニズム

Java EE 8より前は、web.xmlファイルを介して宣言的に認証メカニズムを構成していました。

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

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

また、各実装をトリガーする注釈も提供します。

  1. @BasicAuthenticationMechanismDefinition

  2. @FormAuthenticationMechanismDefinition

  3. @CustomFormAuthenrticationMechanismDefinition

3.1. 基本的なHTTP認証

上記のように、ウェブアプリケーションは@BasicAuthenticationMechanismDefinition annotation on a CDI beanを使用するだけで基本HTTP認証を構成できます。

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

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

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

WWW-Authenticate: Basic realm="userRealm"

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

//user=example, password=example
Authorization: Basic YmFlbGR1bmc6YmFlbGR1bmc=

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

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

サーブレット仕様で定義されているThe @FormAuthenticationMechanismDefinition annotation triggers a form-based authentication

次に、ログインページとエラーページを指定するか、デフォルトの妥当なページ/login/login-errorを使用するオプションがあります。

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

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

次に、クライアントはフォームを、コンテナによって提供される事前定義のバッキング認証プロセスに送信する必要があります。

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'slogin()メソッドに送信します。

//...

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

HttpAuthenticationMechanismインターフェースは、3つのメソッドを定義します。 The most important is the 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 byを介してIdentityStoreに委任することをお勧めします。

また、CDIを有効にする必要があるため、実装に@ApplicationScopedアノテーションを付けました。

資格情報の有効な検証、および最終的なユーザーロールの取得後、the implementation should notify the container then

HttpMessageContext.notifyContainerAboutLogin(Principal principal, Set groups)

3.5. サーブレットのセキュリティの実施

A web application can enforce security constraints by using the @ServletSecurity annotation on a Servlet implementation

@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 {
}

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

次に、コンテナは、url-patternおよびHTTPメソッドごとに、接続されたユーザーがリソースにアクセスするための適切なロールを持っているかどうかを確認します。

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

この機能はthe IdentityStore interface, and it’s used to validate credentials and eventually retrieve group membership. によって抽象化されます。つまり、認証、承認、またはその両方の機能を提供できます.

IdentityStoreは、呼び出されたIdentityStoreHandlerインターフェースを介してHttpAuthenticationMecanismが使用することを目的としており、推奨されています。 IdentityStoreHandlerのデフォルトの実装は、サーブレット containerによって提供されます。

アプリケーションは、IdentityStoreの実装を提供するか、データベースとLDAPのコンテナーによって提供される2つの組み込み実装のいずれかを使用できます。

4.1. 組み込みのIDストア

Java EE準拠のサーバーは、the two Identity Stores: Database and 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 {
}

構成データとして、we need a JNDI data source to an external database、呼び出し元とそのグループをチェックするための2つのJDBCステートメント、最後に複数ストアの場合に使用される優先度パラメーターが構成されます。

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

データベースと同様に、構成データを渡すことによるLDAP IdentityStore implementation is initialized through the @LdapIdentityStoreDefinition

@LdapIdentityStoreDefinition(
  url = "ldap://localhost:10389",
  callerBaseDn = "ou=caller,dc=example,dc=com",
  groupSearchBase = "ou=group,dc=example,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 getCallerGroups(
  CredentialValidationResult validationResult)
default int priority()
default Set validationTypes()

priority() メソッドは、この実装がIdentityStoreHandler.によって処理される反復順序の値を返します。優先度の低いIdentityStoreが最初に処理されます。

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

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

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

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

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

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

    @Override
    public Set 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 validationTypes() {
    return EnumSet.of(ValidationType.PROVIDE_GROUPS);
}

次に、getCallerGroups()メソッドの実装を提供する必要があります。

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

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

    @Override
    public Set validationTypes() {
        return EnumSet.of(ValidationType.PROVIDE_GROUPS);
    }

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

IdentityStoreHandlerは実装がCDI Beanであることを想定しているため、ApplicationScopedアノテーションで装飾します。

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

Java EE 8 Security APIはan access point to programmatic security through the SecurityContext interfaceを提供します。 これは、コンテナによって適用される宣言型セキュリティモデルが十分でない場合の代替手段です。

SecurityContextインターフェースのデフォルトの実装は、実行時にCDI Beanとして提供される必要があるため、次のように挿入する必要があります。

@Inject
SecurityContext securityContext;

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

5.1. 発信者データの取得

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

サーブレットコンテナでHttpServletRequestgetUserPrincipal()メソッドと isUserInRole()メソッドを使用しますが、EJBContextgetCallerPrincipal() isCallerInRole() methods の同様のメソッドがEJBコンテナ。

新しいJavaEE 8 Security APIは、このby を標準化し、SecurityContextインターフェースを介して同様のメソッドを提供します。

Principal getCallerPrincipal();
boolean isCallerInRole(String role);
 Set getPrincipalsByType(Class type);

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

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

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

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

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

次に、この保護されたリソースへのアクセスを確認するには、hasAccessToWebResource() method:を呼び出す必要があります。

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

この場合、ユーザーがロールUSER_ROLE.にいると、メソッドはtrueを返します。

5.3. プログラムによる呼び出し元の認証

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

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

その後、コンテナは通知を受け、アプリケーションに設定された認証メカニズムを呼び出します。 AuthenticationParametersパラメータは、HttpAuthenticationMechanism:に認証情報を提供します

withParams().credential(credential)

AuthenticationStatusSUCCESSSEND_FAILUREの値は、認証の成功と失敗を設計し、SEND_CONTINUEは、認証プロセスの進行中のステータスを示します。

6. サンプルの実行

これらの例を強調するために、Java EE 8をサポートするOpen Libertyサーバーの最新の開発ビルドを使用しました。 これは、アプリケーションをデプロイしてサーバーを起動することもできるliberty-maven-pluginのおかげで、ダウンロードおよびインストールされます。

サンプルを実行するには、対応するモジュールにアクセスして、このコマンドを呼び出します。

mvn clean package liberty:run

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

7. 結論

この記事では、新しいJava EE 8 Security APIの主な機能の構成と実装について説明しました。

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

いつものように、この記事のコード例は利用可能なover on GitHubです。