このページを編集

guides/security-proactive-authentication.adoc

設定のカスタマイズや例外処理など、Quarkusでプロアクティブ認証を管理する方法を学びます。 さまざまなアプリケーションシナリオに対する実践的な洞察と戦略を得ることができます。

Quarkusでは、プロアクティブ認証がデフォルトで有効になっています。 これにより、ターゲットページが認証を必要としない場合でも、認証情報を持つすべての受信リクエストが認証されます。 その結果、ターゲットページが公開されている場合でも、無効な認証情報を持つリクエストは拒否されます。 匿名リクエストが許可されているため、認証情報のないリクエストは拒否されません。

ターゲット・ページが要求したときにのみ認証を行いたい場合は、このデフォルトの動作をオフにすることができます。 プロアクティブ認証をオフにして、ターゲット・ページが要求したときにのみ認証を行なうようにするには、 application.properties の設定ファイルを以下のように変更します:

quarkus.http.auth.proactive=false

プロアクティブ認証をオフにすると、ID が要求されたときにのみ認証プロセスが実行されます。 ID が要求されるのは、ユーザ認証を必要とするセキュリティ・ルールがあるため、または現在の ID へのプログラムによるアクセスが必要なためです。

プロアクティブ認証が使用されている場合、 SecurityIdentity へのアクセスはブロッキング操作となります。 これは、認証がまだ行われていない可能性があり、 SecurityIdentity へのアクセスにはデータベースなどの外部システムへの呼び出しが必要になるため、操作がブロックされる可能性があるためです。 ブロッキング・アプリケーションの場合、これは問題ではありません。 しかし、リアクティブなアプリケーションで認証を無効にしている場合は、 I/O スレッドでブロッキング操作を行うことができないため、失敗します。 これを回避するには、 io.quarkus.security.identity.CurrentIdentityAssociation のインスタンスを @Inject し、 Uni<SecurityIdentity> getDeferredIdentity(); メソッドを呼び出します。 そして、認証が完了して ID が利用可能になったときに通知されるように、生成された Uni をサブスクライブします。

認証がすでに行われているため、@RolesAllowed@Authenticated、またはそれぞれの設定認可チェックでアノテーションが付けられたエンドポイントから、Quarkus REST (formerly RESTEasy Reactive)public SecurityIdentity getIdentity() を使用して SecurityIdentity に同期的にアクセスできます。 ルート応答が同期的である場合、Reactive ルート にも同じことが当てはまります。

プロアクティブ認証が無効になっている場合、void ではないセキュリティー保護されたメソッドが同期的に値を返すと、CDI Bean で使用される 標準セキュリティーアノテーション は I/O スレッドで機能しません。 この制限は、これらのメソッドが SecurityIdentity にアクセスする必要があることが原因です。

次の例では、HelloResourceHelloService を定義します。 /hello へのすべての GET リクエストは I/O スレッドで実行され、BlockingOperationNotAllowedException 例外が出力されます。

例の修正方法は1つではありません:

  • hello エンドポイントに @Blocking アノテーションを付けて、ワーカースレッドに切り替えます。

  • リアクティブまたは非同期データ型を使用して、sayHello メソッドの戻り値の型を変更します。

  • @RolesAllowed アノテーションをエンドポイントに移動します。 エンドポイントメソッドからの SecurityIdentity へのアクセスは決してブロッキング操作ではないので、これは最も安全な方法のひとつです。

import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.smallrye.mutiny.Uni;

@Path("/hello")
@PermitAll
public class HelloResource {

    @Inject
    HelloService helloService;

    @GET
    public Uni<String> hello() {
        return Uni.createFrom().item(helloService.sayHello());
    }

}
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class HelloService {

    @RolesAllowed("admin")
    public String sayHello() {
        return "Hello";
    }

}

CDI リクエストコンテキストの有効化

認証および認可の際に、@RequestScoped の Bean をインジェクトする必要がある場合があります。 適切な例として、SecurityIdentity の拡張時にデータベースへアクセスするケースが挙げられます。これについては、"Security Tips and Tricks" ガイドの セキュリティーアイデンティティーカスタマイズ セクションで説明されています。 認証や認可の際に jakarta.enterprise.context.ContextNotActiveException が発生する場合、プロアクティブ認証を無効化する のが最も適切な解決策となることが多いです。 また、ユーザーは CDI リクエストコンテキスト を有効化することもできます。たとえば、@ActivateRequestContext アノテーションを使用して有効にできます。ただし、一部の CDI Bean は利用可能な状態になっていない可能性がある点に注意してください。

このソリューションの例外は、アプリケーションエンドポイントが 設定を使用した承認 で保護されている場合です。 詳細は、「Web エンドポイントの認可」ガイドの RequestScoped Bean を HttpSecurityPolicy に挿入する セクションを参照してください。

認証例外応答のカスタマイズ

Jakarta REST ExceptionMapper を使用して、 io.quarkus.security.AuthenticationFailedException のような Quarkus Security 認証の例外をキャプチャできます。 例:

package io.quarkus.it.keycloak;

import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

import io.quarkus.security.AuthenticationFailedException;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFailedExceptionMapper implements ExceptionMapper<AuthenticationFailedException> {

    @Context
    UriInfo uriInfo;

    @Override
    public Response toResponse(AuthenticationFailedException exception) {
        return Response.status(401).header("WWW-Authenticate", "Basic realm=\"Quarkus\"").build();
    }
}
Some HTTP authentication mechanisms must handle authentication exceptions themselves to create a correct authentication challenge. For example, io.quarkus.oidc.runtime.CodeAuthenticationMechanism, which manages OpenID Connect (OIDC) authorization code flow authentication, must build a correct redirect URL and set a state cookie. Therefore, avoid using custom exception mappers to customize authentication exceptions thrown by such mechanisms. Instead, a safer approach is to ensure that proactive authentication is enabled and to use Vert.x HTTP route failure handlers. This is because events come to the handler with the correct response status and headers. Then, you must only customize the response; for example:
package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

import io.quarkus.security.AuthenticationFailedException;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class AuthenticationFailedExceptionHandler {

    public void init(@Observes Router router) {
        router.route().failureHandler(new Handler<RoutingContext>() {
            @Override
            public void handle(RoutingContext event) {
                if (event.failure() instanceof AuthenticationFailedException) {
                    event.response().end("CUSTOMIZED_RESPONSE");
                } else {
                    event.next();
                }
            }
        });
    }
}

関連コンテンツ