第4章 プロアクティブ認証
設定のカスタマイズや例外の処理を含め、Quarkus でプロアクティブ認証を管理する方法を説明します。さまざまなアプリケーションシナリオにおける実用的な洞察とストラテジーを得ることができます。
Quarkus では、プロアクティブ認証がデフォルトで有効になっています。これにより、ターゲットページで認証が不要な場合でも、認証情報を持つすべての受信リクエストが必ず認証されます。その結果、ターゲットページが公開されている場合でも、無効な認証情報を持つリクエストは拒否されます。
ターゲットページで必要な場合にのみ認証を行う場合は、このデフォルトの動作をオフにできます。ターゲットページで要求される場合にのみ認証を行うためにプロアクティブ認証をオフにするには、application.properties
設定ファイルを次のように変更します。
quarkus.http.auth.proactive=false
プロアクティブ認証をオフにすると、アイデンティティーが要求された場合にのみ認証プロセスが実行されます。ユーザーの認証を必要とするセキュリティールールがある場合、または現在のアイデンティティーへのプログラムによるアクセスが必要な場合、アイデンティティーが要求される場合があります。
プロアクティブ認証が使用されている場合、SecurityIdentity
へのアクセスはブロック操作になります。これは、認証がまだ行われておらず、SecurityIdentity
へのアクセスにはデータベースなどの外部システムへの呼び出しが必要になり、操作がブロックされる可能性があるためです。ブロッキングアプリケーションの場合、これは問題になりません。しかし、リアクティブアプリケーションで認証を無効にしている場合は、I/O スレッドでブロッキング操作を実行できないため、これは失敗します。この問題を回避するには、io.quarkus.security.identity.CurrentIdentityAssociation
のインスタンスに対して @Inject
を実行し、Uni<SecurityIdentity> getDeferredIdentity();
メソッドを呼び出す必要があります。次に、その結果として得られる Uni
をサブスクライブすると、認証が完了してアイデンティティーが利用可能になった場合に通知を受け取ることができます。
認証がすでに行われているため、@RolesAllowed
、@Authenticated
、またはそれぞれの設定認可チェックでアノテーションが付けられたエンドポイントから、Quarkus REST (旧称 RESTEasy Reactive) の public SecurityIdentity getIdentity()
を使用して SecurityIdentity
に同期的にアクセスできます。ルートレスポンスが同期的である場合、これは Reactive ルート にも当てはまります。
プロアクティブ認証が無効になっている場合、void ではない保護されたメソッドが同期的に値を返すと、CDI Bean で使用される 標準のセキュリティーアノテーション は I/O スレッドで機能しません。これらのメソッドが SecurityIdentity
にアクセスする必要があるため、このような制限が生じます。
次の例では、HelloResource
と HelloService
を定義します。/hello
へのすべての GET リクエストは I/O スレッドで実行され、BlockingOperationNotAllowedException
例外を出力します。
この例は、複数の方法で修正できます。
-
hello
エンドポイントにアノテーション@Blocking
を付け、ワーカースレッドに切り替えます。 -
リアクティブまたは非同期データ型を使用して、
sayHello
メソッドの戻り値の型を変更します。 -
@RolesAllowed
アノテーションをエンドポイントに移動します。エンドポイントメソッドからSecurityIdentity
にアクセスしてもブロック操作になることはないため、これは最も安全な方法の 1 つです。
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"; } }
4.1. 認証例外レンスポンスをカスタマイズする
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(); } }
一部の HTTP 認証メカニズムでは、正しい認証チャレンジを作成するために、認証例外を自ら処理する必要があります。たとえば、OpenID Connect (OIDC) 認可コードフロー認証を管理する io.quarkus.oidc.runtime.CodeAuthenticationMechanism
は、正しいリダイレクト URL を作成し、状態 Cookie を設定する必要があります。したがって、カスタム例外マッパーを使用して、このようなメカニズムによって出力される認証例外をカスタマイズしないでください。代わりのより安全なアプローチとして、プロアクティブ認証を有効にして Vert.x HTTP ルート失敗ハンドラーを使用できます。なぜなら、イベントは正しいレスポンスステータスとヘッダーとともにハンドラーに送信されるからです。次に、レンスポンスのみをカスタマイズする必要があります。以下はその例です。
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(); } } }); } }