主动身份验证
了解如何在 Quarkus 中管理主动认证,包括自定义设置和处理异常。获取适用于各种应用场景的实用见解和策略。
默认情况下,Quarkus 中启用了主动认证。它确保所有带有凭据的传入请求都经过身份验证,即使目标页面不需要身份验证也是如此。因此,带有无效凭据的请求会被拒绝,即使目标页面是公开的。没有凭据的请求不会被拒绝,因为允许匿名请求。
如果您只想在目标页面需要时才进行身份验证,您可以关闭此默认行为。要关闭主动认证,以便仅在目标页面需要时才进行身份验证,请按如下方式修改 application.properties
配置文件
quarkus.http.auth.proactive=false
如果您关闭主动认证,则仅当请求身份时才运行身份验证过程。请求身份可能是因为安全规则要求用户进行身份验证,或者因为需要以编程方式访问当前身份。
如果未使用主动认证,则访问 SecurityIdentity
是一个阻塞操作。这是因为身份验证可能尚未发生,并且访问 SecurityIdentity
可能需要调用外部系统(例如数据库),这可能会阻塞操作。对于阻塞应用程序,这不是问题。但是,如果您在反应式应用程序中禁用了身份验证,则此操作将失败,因为您无法在 I/O 线程上执行阻塞操作。要解决此问题,您需要 @Inject
一个 io.quarkus.security.identity.CurrentIdentityAssociation
的实例,并调用 Uni<SecurityIdentity> getDeferredIdentity();
方法。然后,您可以订阅生成的 Uni
,以便在身份验证完成且身份可用时收到通知。
您仍然可以使用 |
禁用主动身份验证后,如果安全的非 void 方法同步返回一个值,则在 CDI bean 上使用的标准安全注解在 I/O 线程上不起作用。此限制源于这些方法需要访问 SecurityIdentity
。
以下示例定义了 HelloResource
和 HelloService
。对 /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";
}
}
激活 CDI 请求上下文
您可能需要在身份验证和授权期间注入 @RequestScoped
bean。一个很好的例子是在 SecurityIdentity
增强期间访问数据库,这在“安全提示和技巧”指南的安全身份自定义部分中进行了描述。如果身份验证或授权失败并出现 jakarta.enterprise.context.ContextNotActiveException
,则禁用主动身份验证通常是最佳解决方案。用户还可以激活 CDI 请求上下文,例如,通过使用 @ActivateRequestContext
注释。但是,某些 CDI bean 可能尚未准备好使用。
此解决方案的一个例外情况是,当应用程序端点使用使用配置进行授权进行保护时。有关更多信息,请参见“Web 端点授权”指南的将 RequestScoped bean 注入 HttpSecurityPolicy部分。
自定义身份验证异常响应
您可以使用 Jakarta REST ExceptionMapper
来捕获 Quarkus Security 身份验证异常,例如 io.quarkus.security.AuthenticationFailedException
。例如
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();
}
}
});
}
}