プロアクティブ認証
設定のカスタマイズや例外処理など、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
をサブスクライブします。
You can still access |
When proactive authentication is disabled, standard security annotations used on CDI beans do not function on an I/O thread if a secured method that is not void synchronously returns a value.
This limitation arises from the necessity for these methods to access SecurityIdentity
.
The following example defines HelloResource
and HelloService
.
Any GET request to /hello
runs on the I/O thread and throws a BlockingOperationNotAllowedException
exception.
例の修正方法は1つではありません:
-
Switch to a worker thread by annotating the
hello
endpoint with@Blocking
. -
Change the
sayHello
method return type by using a reactive or asynchronous data type. -
@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";
}
}
認証例外応答のカスタマイズ
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();
}
}
});
}
}