Proactive authentication

了解如何在 Quarkus 中管理主动身份验证,包括自定义设置和处理异常。获得适用于各种应用场景的实际见解和策略。

Learn how to manage proactive authentication in Quarkus, including customizing settings and handling exceptions. Gain practical insights and strategies for various application scenarios.

Quarkus 中默认启用了主动身份验证。它确保使用凭证的传入请求得到验证,即使目标页面不需要验证也是如此。结果,即使目标页面是公开的,也会拒绝带有无效凭证的请求。

Proactive authentication is enabled in Quarkus by default. It ensures that all incoming requests with credentials are authenticated, even if the target page does not require authentication. As a result, requests with invalid credentials are rejected, even if the target page is public.

如果你只想在目标页面需要时才进行身份验证,可以关闭此默认行为。要关闭主动身份验证,以便仅在目标页面需要时进行身份验证,请如下修改 application.properties 配置文件:

You can turn off this default behavior if you want to authenticate only when the target page requires it. To turn off proactive authentication so that authentication occurs only when the target page requires it, modify the application.properties configuration file as follows:

quarkus.http.auth.proactive=false

如果你关闭主动身份验证,身份验证进程仅在请求身份时才运行。由于需要用户身份验证的安全规则或由于需要以编程方式访问当前身份,因此可以请求身份。

If you turn off proactive authentication, the authentication process runs only when an identity is requested. An identity can be requested because of security rules that require the user to authenticate or because programmatic access to the current identity is required.

如果使用主动身份验证,则访问 SecurityIdentity 是一个阻塞操作。这是因为身份验证可能尚未发生,并且访问 SecurityIdentity 可能需要调用可能阻塞操作的外部系统(例如数据库)。对于阻塞应用程序,这不是一个问题。但是,如果你已在响应式应用程序中禁用身份验证,则会出现错误,因为你无法在 I/O 线程上执行阻塞操作。要解决此问题,你需要 @Inject 一个 io.quarkus.security.identity.CurrentIdentityAssociation 实例并调用 Uni<SecurityIdentity> getDeferredIdentity(); 方法。然后,你可以订阅结果 Uni 以在身份验证完成后和身份可用时得到通知。

If proactive authentication is used, 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 fails 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 to be notified when authentication is complete and the identity is available.

你仍然可以使用 public SecurityIdentity getIdentity()Quarkus REST (formerly RESTEasy Reactive) 中的 @RolesAllowed@Authenticated 所注释的端点中同步访问 SecurityIdentity,或者在有相应配置授权检查的情况下进行访问,因为身份验证已经发生了。如果路由响应是同步的,Reactive routes 也同样适用。

You can still access SecurityIdentity synchronously with public SecurityIdentity getIdentity() in Quarkus REST (formerly 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.

当主动身份验证被禁用时,如果一个非空返回一个值的、安全的非同步方法返回一个值,则 CDI bean 上使用的 standard security annotations 将不会在 I/O 线程上运行。这种限制源于这些方法必须访问 SecurityIdentity 的必要性。

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.

以下示例定义 HelloResourceHelloService。对 /hello 的任何 GET 请求都在 I/O 线程上运行,并抛出一个 BlockingOperationNotAllowedException 异常。

The following example defines HelloResource and HelloService. Any GET request to /hello runs on the I/O thread and throws a BlockingOperationNotAllowedException exception.

有不止一种办法可以修复示例:

There is more than one way to fix the example:

  • 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.

  • Move the @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";
    }

}

Customize authentication exception responses

你可以使用 Jakarta REST ExceptionMapper 来捕获 Quarkus 安全身份验证异常,如 io.quarkus.security.AuthenticationFailedException。例如:

You can use Jakarta REST ExceptionMapper to capture Quarkus Security authentication exceptions such as io.quarkus.security.AuthenticationFailedException. For example:

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();
    }
}

一些 HTTP 身份验证机制必须自己处理身份验证异常,以创建正确的身份验证质询。例如,管理 OpenID Connect(OIDC)授权码流程身份验证的 io.quarkus.oidc.runtime.CodeAuthenticationMechanism 必须构建一个正确的重定向 URL,并设置一个状态 Cookie。因此,请避免使用自定义异常映射器来定制此类机制抛出的身份验证异常。相反,一种更安全的方法是确保主动身份验证处于启用状态,并使用 Vert.x HTTP 路由故障处理程序。这是因为事件将带有正确的响应状态和标头来到处理程序。然后,你只需要定制响应;例如:

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();
                }
            }
        });
    }
}