Proactive authentication

Quarkusでは、プロアクティブ認証がデフォルトで有効になっています。つまり、受信リクエストにクレデンシャルがある場合、ターゲットページで認証が必要ない場合でも、そのリクエストは常に認証されることになります。

無効なクレデンシャルを含むリクエストは、ページが公開されている場合でも、常に拒否されます。

If you only want to authenticate when the target page requires authentication, you can change the default behavior.

Quarkusでプロアクティブ認証を無効にするには、 application.properties 設定ファイルで次の属性を設定します:

quarkus.http.auth.proactive=false

プロアクティブ認証を無効にすると、アイデンティティーが要求されたときにのみ、認証プロセスが実行されます。アイデンティティーが要求されるのは、ユーザーを認証する必要があるセキュリ ティルールールや、現在の アイデンティティーへのプログラムによるアクセスによってです。

If proactive authentication is in use, accessing SecurityIdentity is a blocking operation. This is because authentication might have yet to happen and accessing SecurityIdentity might require calls to external systems, such as databases, that might block the operation. For blocking applications, this is not an issue. However, if you have disabled authentication in a reactive application, this will fail because you cannot do blocking operations on the I/O thread. To work around this, you need to @Inject an instance of io.quarkus.security.identity.CurrentIdentityAssociation and call the Uni<SecurityIdentity> getDeferredIdentity(); method. Then, you can subscribe to the resulting Uni and will be notified when authentication is complete and the identity is available.

You can still access SecurityIdentity synchronously with public SecurityIdentity getIdentity() in RESTEasy Reactive from endpoints that are annotated with @RolesAllowed, @Authenticated, or with respective configuration authorization checks because authentication has already happened. The same is also valid for Reactive routes if a route response is synchronous.

Standard security annotations on CDI beans are not supported on an I/O thread if a non-void secured method returns a value synchronously and proactive authentication is disabled because they need to access SecurityIdentity.

In the following example, HelloResource and HelloService are defined. Any GET request to /hello will run on the I/O thread and throw a BlockingOperationNotAllowedException exception.

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

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

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

  • Move @RolesAllowed annotation to the endpoint. This could be one of the safest ways because accessing SecurityIdentity from endpoint methods is never the blocking operation.

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.Response;
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 need to 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, needs to build a correct redirect URL, cookies, and so on. For that reason, 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. You then only need to customize the response, as shown in the following 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();
                }
            }
        });
    }
}