Proactive authentication

了解如何在 Quarkus 中管理主动身份验证,包括自定义设置和处理异常。获得适用于各种应用场景的实际见解和策略。 Quarkus 中默认启用了主动身份验证。它确保使用凭证的传入请求得到验证,即使目标页面不需要验证也是如此。结果,即使目标页面是公开的,也会拒绝带有无效凭证的请求。 如果你只想在目标页面需要时才进行身份验证,可以关闭此默认行为。要关闭主动身份验证,以便仅在目标页面需要时进行身份验证,请如下修改 application.properties 配置文件:

quarkus.http.auth.proactive=false

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

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

当主动身份验证被禁用时,如果一个非空返回一个值的、安全的非同步方法返回一个值,则 CDI bean 上使用的 standard security annotations 将不会在 I/O 线程上运行。这种限制源于这些方法必须访问 SecurityIdentity 的必要性。 以下示例定义 HelloResourceHelloService。对 /hello 的任何 GET 请求都在 I/O 线程上运行,并抛出一个 BlockingOperationNotAllowedException 异常。 有不止一种办法可以修复示例:

  • 通过用 @Blocking 注释 hello 端点来切换到一个工作程序线程。

  • 通过使用响应式或异步数据类型来更改 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";
    }

}

Customize authentication exception responses

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

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 路由故障处理程序。这是因为事件将带有正确的响应状态和标头来到处理程序。然后,你只需要定制响应;例如:

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