Security Tips and Tricks
Quarkus Security Dependency
io.quarkus:quarkus-security
模块包含核心 Quarkus 安全类。
io.quarkus:quarkus-security
module contains the core Quarkus Security classes.
在大多数情况下,它不必直接添加到项目的构建文件中,因为所有安全扩展都已提供了它。但是,如果你需要编写自己的自定义安全代码(例如,注册一个 Custom Jakarta REST SecurityContext)或使用 BouncyCastle 库,那么请确保已包含该模块:
In most cases, it does not have to be added directly to your project’s build file as it is already provided by all the security extensions. However, if you need to write your own custom security code (for example, register a jaxrs-security-context) or use bouncy-castle libraries, then please make sure it is included:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
implementation("io.quarkus:quarkus-security")
HttpAuthenticationMechanism Customization
可以通过注册 CDI 实现 Bean 来自定义 HttpAuthenticationMechanism
。在下面的示例中,自定义验证器委托给 quarkus-smallrye-jwt
提供的 JWTAuthMechanism
:
One can customize HttpAuthenticationMechanism
by registering a CDI implementation bean.
In the example below the custom authenticator delegates to JWTAuthMechanism
provided by quarkus-smallrye-jwt
:
@Alternative
@Priority(1)
@ApplicationScoped
public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism {
private static final Logger LOG = LoggerFactory.getLogger(CustomAwareJWTAuthMechanism.class);
@Inject
JWTAuthMechanism delegate;
@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
// do some custom action and delegate
return delegate.authenticate(context, identityProviderManager);
}
@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return delegate.getChallenge(context);
}
@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return delegate.getCredentialTypes();
}
@Override
public Uni<HttpCredentialTransport> getCredentialTransport() {
return delegate.getCredentialTransport();
}
}
|
The |
Dealing with more than one HttpAuthenticationMechanism
可以组合多个 HttpAuthenticationMechanism
,例如,必须使用 Quarkus 中提供的内置 Basic
或 JWT
机制来验证作为 HTTP Authorization
Basic
或 Bearer
架构值传递的服务客户端凭据,而必须使用 quarkus-oidc
中提供的 Authorization Code
机制来使用 Keycloak 或其他 OpenID Connect 提供程序对用户进行身份验证。
More than one HttpAuthenticationMechanism
can be combined, for example, the built-in Basic
or JWT
mechanism provided by quarkus-smallrye-jwt
has to be used to verify the service clients credentials passed as the HTTP Authorization
Basic
or Bearer
scheme values while the Authorization Code
mechanism provided by quarkus-oidc
has to be used to authenticate the users with Keycloak or other OpenID Connect providers.
在这些情况下,机制逐次要求验证凭据,直至创建一个 SecurityIdentity
。机制按优先级降序排列。Basic
身份验证机制具有最优先级的 2000
,紧跟其后的是优先级为 1001
的 Authorization Code
机制,Quarkus 提供的所有其他机制的优先级为 1000
。
In such cases the mechanisms are asked to verify the credentials in turn until a SecurityIdentity
is created. The mechanisms are sorted in the descending order using their priority. Basic
authentication mechanism has the highest priority of 2000
, followed by the Authorization Code
one with the priority of 1001
, with all other mechanisms provided by Quarkus having the priority of 1000
.
如果没有提供凭据,则创建机制特定的质询,例如,401
状态由 Basic
或 JWT
机制返回,重定向用户至 OpenID Connect 提供程序的 URL 由 quarkus-oidc
返回,依此类推。
If no credentials are provided then the mechanism specific challenge is created, for example, 401
status is returned by either Basic
or JWT
mechanisms, URL redirecting the user to the OpenID Connect provider is returned by quarkus-oidc
, etc.
因此,如果 Basic
和 Authorization Code
机制相结合,则如果没有提供凭据,则返回 401
,如果 JWT
和 Authorization Code
机制相结合,则返回重定向 URL。
So if Basic
and Authorization Code
mechanisms are combined then 401
will be returned if no credentials are provided and if JWT
and Authorization Code
mechanisms are combined then a redirect URL will be returned.
在一些情况下,选择质询的这种默认逻辑恰好是给定应用程序所需的,但有时可能无法满足要求。在这种情况下(或在确实其他类似情况下,你需要更改请求机制处理当前身份验证或质询请求的顺序),你可以创建自定义机制并选择哪个机制应创建质询,例如:
In some cases such a default logic of selecting the challenge is exactly what is required by a given application, but sometimes it may not meet the requirements. In such cases (or indeed in other similar cases where you’d like to change the order in which the mechanisms are asked to handle the current authentication or challenge request), you can create a custom mechanism and choose which mechanism should create a challenge, for example:
@Alternative 1
@Priority(1)
@ApplicationScoped
public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism {
private static final Logger LOG = LoggerFactory.getLogger(CustomAwareJWTAuthMechanism.class);
@Inject
JWTAuthMechanism jwt;
@Inject
OidcAuthenticationMechanism oidc;
@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
return selectBetweenJwtAndOidc(context).authenticate(context, identityProviderManager);
}
@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return selectBetweenJwtAndOidcChallenge(context).getChallenge(context);
}
@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
Set<Class<? extends AuthenticationRequest>> credentialTypes = new HashSet<>();
credentialTypes.addAll(jwt.getCredentialTypes());
credentialTypes.addAll(oidc.getCredentialTypes());
return credentialTypes;
}
@Override
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return selectBetweenJwtAndOidc(context).getCredentialTransport(context);
}
private HttpAuthenticationMechanism selectBetweenJwtAndOidc(RoutingContext context) {
....
}
private HttpAuthenticationMechanism selectBetweenJwtAndOidcChallenge(RoutingContext context) {
// for example, if no `Authorization` header is available and no `code` parameter is provided - use `jwt` to create a challenge
}
}
1 | Declaring the mechanism an alternative bean ensures this mechanism is used rather than OidcAuthenticationMechanism and JWTAuthMechanism . |
Security Identity Customization
在内部,身份提供程序创建并更新 io.quarkus.security.identity.SecurityIdentity
类的实例,该实例保存用于对客户端(用户)及其其他安全属性进行身份验证的主体、角色和凭据。自定义 SecurityIdentity
的一个简单选项是注册自定的 SecurityIdentityAugmentor
。例如,下面的增强程序添加了一个附加角色:
Internally, the identity providers create and update an instance of the io.quarkus.security.identity.SecurityIdentity
class which holds the principal, roles, credentials which were used to authenticate the client (user) and other security attributes. An easy option to customize SecurityIdentity
is to register a custom SecurityIdentityAugmentor
. For example, the augmentor below adds an addition role:
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.function.Supplier;
@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
return Uni.createFrom().item(build(identity));
// Do 'return context.runBlocking(build(identity));'
// if a blocking call is required to customize the identity
}
private Supplier<SecurityIdentity> build(SecurityIdentity identity) {
if(identity.isAnonymous()) {
return () -> identity;
} else {
// create a new builder and copy principal, attributes, credentials and roles from the original identity
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
// add custom role source here
builder.addRole("dummy");
return builder::build;
}
}
}
这里有另一个示例,展示如何使用当前 mutual TLS (mTLS) authentication 请求中可用的客户端证书来添加更多角色:
Here is another example showing how to use the client certificate available in the current mutual TLS (mTLS) authentication request to add more roles:
import java.security.cert.X509Certificate;
import io.quarkus.security.credential.CertificateCredential;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.function.Supplier;
import java.util.Set;
@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
return Uni.createFrom().item(build(identity));
}
private Supplier<SecurityIdentity> build(SecurityIdentity identity) {
// create a new builder and copy principal, attributes, credentials and roles from the original identity
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
CertificateCredential certificate = identity.getCredential(CertificateCredential.class);
if (certificate != null) {
builder.addRoles(extractRoles(certificate.getCertificate()));
}
return builder::build;
}
private Set<String> extractRoles(X509Certificate certificate) {
String name = certificate.getSubjectX500Principal().getName();
switch (name) {
case "CN=client":
return Collections.singleton("user");
case "CN=guest-client":
return Collections.singleton("guest");
default:
return Collections.emptySet();
}
}
}
如果注册了多个自定义 If more than one custom |
默认情况下,在增强安全身份时不会激活请求上下文,这意味着如果你想使用例如强制使用请求上下文的 Hibernate,你将拥有一个 jakarta.enterprise.context.ContextNotActiveException
。
By default, the request context is not activated when augmenting the security identity, this means that if you want to use for example Hibernate
that mandates a request context, you will have a jakarta.enterprise.context.ContextNotActiveException
.
解决办法是激活请求上下文,以下示例展示了如何使用 Panache UserRoleEntity
获取 Hibernate 中的角色。
The solution is to activate the request context, the following example shows how to get the roles from an Hibernate with Panache UserRoleEntity
.
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
@Inject
Instance<SecurityIdentitySupplier> identitySupplierInstance;
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
if(identity.isAnonymous()) {
return Uni.createFrom().item(identity);
}
// Hibernate ORM is blocking
SecurityIdentitySupplier identitySupplier = identitySupplierInstance.get();
identitySupplier.setIdentity(identity);
return context.runBlocking(identitySupplier);
}
}
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.control.ActivateRequestContext;
import java.util.function.Supplier;
@Dependent
class SecurityIdentitySupplier implements Supplier<SecurityIdentity> {
private SecurityIdentity identity;
@Override
@ActivateRequestContext
public SecurityIdentity get() {
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
String user = identity.getPrincipal().getName();
UserRoleEntity.<userRoleEntity>streamAll()
.filter(role -> user.equals(role.user))
.forEach(role -> builder.addRole(role.role));
return builder.build();
}
public void setIdentity(SecurityIdentity identity) {
this.identity = identity;
}
}
上面示例中显示的 CDI 请求上下文激活无法帮助你访问启用主动身份验证时的 RoutingContext
。以下示例说明如何从 SecurityIdentityAugmentor
访问 RoutingContext
:
The CDI request context activation shown in the example above does not help you to access the RoutingContext
when the proactive authentication is enabled.
The following example illustrates how you can access the RoutingContext
from the SecurityIdentityAugmentor
:
package org.acme.security;
import java.util.Map;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomSecurityIdentityAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context,
Map<String, Object> attributes) {
RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(attributes);
if (routingContext != null) {
// Augment SecurityIdentity using RoutingContext
} else {
return augment(identity, context); 1
}
}
...
}
1 | The RoutingContext is not be available when the SecurityIdentity is augmented after HTTP request has completed. |
如果你实施了一个自定的 |
If you implemented a custom |
Custom Jakarta REST SecurityContext
如果你使用 Jakarta REST ContainerRequestFilter
设置一个自定义 Jakarta REST SecurityContext
,则确保 ContainerRequestFilter
在 Jakarta REST 预匹配阶段运行,方法是为它添加一个 @PreMatching
注释,以便此自定义安全上下文与 Quarkus SecurityIdentity
链接,例如:
If you use Jakarta REST ContainerRequestFilter
to set a custom Jakarta REST SecurityContext
then make sure ContainerRequestFilter
runs in the Jakarta REST pre-match phase by adding a @PreMatching
annotation to it for this custom security context to be linked with Quarkus SecurityIdentity
, for example:
import java.security.Principal;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.ext.Provider;
@Provider
@PreMatching
public class SecurityOverrideFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String user = requestContext.getHeaders().getFirst("User");
String role = requestContext.getHeaders().getFirst("Role");
if (user != null && role != null) {
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return user;
}
};
}
@Override
public boolean isUserInRole(String r) {
return role.equals(r);
}
@Override
public boolean isSecure() {
return false;
}
@Override
public String getAuthenticationScheme() {
return "basic";
}
});
}
}
}
Disabling Authorization
如果你有充分的理由禁用授权,则可以注册自定的 AuthorizationController
:
If you have a good reason to disable the authorization then you can register a custom AuthorizationController
:
@Alternative
@Priority(Interceptor.Priority.LIBRARY_AFTER)
@ApplicationScoped
public class DisabledAuthController extends AuthorizationController {
@ConfigProperty(name = "disable.authorization", defaultValue = "false")
boolean disableAuthorization;
@Override
public boolean isAuthorizationEnabled() {
return !disableAuthorization;
}
}
对于手动测试,Quarkus 提供了一个方便的配置属性,用于在开发模式下禁用授权。此属性与上面显示的自定义 AuthorizationController
具有完全相同的效果,但仅在开发模式下可用:
For manual testing Quarkus provides a convenient config property to disable authorization in dev mode. This property has the exact same effect as the custom AuthorizationController
shown above, but is only available in dev mode:
quarkus.security.auth.enabled-in-dev-mode=false
另请参阅 TestingSecurity Annotation 部分,了解如何使用 TestSecurity
注释禁用安全检查。
Please also see TestingSecurity Annotation section on how to disable the security checks using TestSecurity
annotation.
Registering Security Providers
Default providers
在原生模式下运行时,GraalVM 原生可执行文件生成的默认行为是仅包含主要“SUN”提供程序,除非你已启用 SSL,在这种情况下会注册所有安全提供程序。如果你不使用 SSL,则可以使用 quarkus.security.security-providers
属性按名称有选择地注册安全提供程序。以下示例说明了注册“SunRsaSign”和“SunJCE”安全提供程序的配置:
When running in native mode, the default behavior for GraalVM native executable generation is to only include the main "SUN" provider
unless you have enabled SSL, in which case all security providers are registered. If you are not using SSL, then you can selectively
register security providers by name using the quarkus.security.security-providers
property. The following example illustrates
configuration to register the "SunRsaSign" and "SunJCE" security providers:
quarkus.security.security-providers=SunRsaSign,SunJCE
BouncyCastle
如果你需要注册一个 org.bouncycastle.jce.provider.BouncyCastleProvider
JCE 提供程序,请设置一个 BC
提供程序名称:
If you need to register an org.bouncycastle.jce.provider.BouncyCastleProvider
JCE provider then please set a BC
provider name:
quarkus.security.security-providers=BC
并添加 BouncyCastle 提供程序依赖项:
and add the BouncyCastle provider dependency:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
implementation("org.bouncycastle:bcprov-jdk18on")
BouncyCastle JSSE
如果您需要注册 org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
JSSE 提供程序并使用该提供程序代替默认的 SunJSSE 提供程序,请设置 BCJSSE
提供程序名称:
If you need to register an org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
JSSE provider and use it instead of the default SunJSSE provider then please set a BCJSSE
provider name:
quarkus.security.security-providers=BCJSSE
quarkus.http.ssl.client-auth=REQUIRED
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=password
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=password
并添加 BouncyCastle TLS 依赖项:
and add the BouncyCastle TLS dependency:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
</dependency>
implementation("org.bouncycastle:bctls-jdk18on")
BouncyCastle FIPS
如果您需要注册 org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
JCE 提供程序,请设置 BCFIPS
提供程序名称:
If you need to register an org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
JCE provider then please set a BCFIPS
provider name:
quarkus.security.security-providers=BCFIPS
并添加 BouncyCastle FIPS 提供程序依赖项:
and add the BouncyCastle FIPS provider dependency:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
</dependency>
implementation("org.bouncycastle:bc-fips")
|
BouncyCastle JSSE FIPS
如果您需要注册 org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
JSSE 提供程序并使用该提供程序与 org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
结合使用,代替默认的 SunJSSE 提供程序,请设置 BCFIPSJSSE
提供程序名称:
If you need to register an org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
JSSE provider and use it in combination with org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
instead of the default SunJSSE provider then please set a BCFIPSJSSE
provider name:
quarkus.security.security-providers=BCFIPSJSSE
quarkus.http.ssl.client-auth=REQUIRED
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=password
quarkus.http.ssl.certificate.key-store-file-type=BCFKS
quarkus.http.ssl.certificate.key-store-provider=BCFIPS
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=password
quarkus.http.ssl.certificate.trust-store-file-type=BCFKS
quarkus.http.ssl.certificate.trust-store-provider=BCFIPS
以及针对使用 BouncyCastle FIPS 提供程序进行了优化的 BouncyCastle TLS 依赖项:
and the BouncyCastle TLS dependency optimized for using the BouncyCastle FIPS provider:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-fips</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
</dependency>
implementation("org.bouncycastle:bctls-fips")
implementation("org.bouncycastle:bc-fips")
请注意,密钥库和信任库类型及提供程序已设置为 BCFKS
和 BCFIPS
。可以像这样使用此类型和提供程序生成密钥库:
Note that the keystore and truststore type and provider are set to BCFKS
and BCFIPS
.
One can generate a keystore with this type and provider like this:
keytool -genkey -alias server -keyalg RSA -keystore server-keystore.jks -keysize 2048 -keypass password -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath $PATH_TO_BC_FIPS_JAR -storetype BCFKS
|
SunPKCS11
SunPKCS11
提供程序提供了一个桥梁连接到特定的 PKCS#11
实现,例如加密智能卡和其他硬件安全模块、FIPS 模式下的网络安全服务等。
SunPKCS11
provider provides a bridge to specific PKCS#11
implementations such as cryptographic smartcards and other Hardware Security Modules, Network Security Services in FIPS mode, etc.
通常,为了使用 SunPKCS11
,需要安装 PKCS#11
实现,生成一个配置,该配置通常引用一个共享库、令牌插槽等,并编写以下 Java 代码:
Typically, in order to work with SunPKCS11
, one needs to install a PKCS#11
implementation, generate a configuration which usually refers to a shared library, token slot, etc and write the following Java code:
import java.security.Provider;
import java.security.Security;
String configuration = "pkcs11.cfg"
Provider sunPkcs11 = Security.getProvider("SunPKCS11");
Provider pkcsImplementation = sunPkcs11.configure(configuration);
// or prepare configuration in the code or read it from the file such as "pkcs11.cfg" and do
// sunPkcs11.configure("--" + configuration);
Security.addProvider(pkcsImplementation);
在 Quarkus 中,您只需在配置级别就能实现相同的功能,而无需修改代码,例如:
In Quarkus you can achieve the same at the configuration level only without having to modify the code, for example:
quarkus.security.security-providers=SunPKCS11
quarkus.security.security-provider-config.SunPKCS11=pkcs11.cfg
请注意,虽然在本地镜像中支持访问 Note that while accessing the |
Reactive Security
如果您打算在响应式环境中使用安全性,则可能需要 SmallRye Context Propagation:
If you are going to use security in a reactive environment, you will likely need SmallRye Context Propagation:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-context-propagation")
这将允许您在整个响应式回调中传播身份。您还需要确保您正在使用能够传播身份的执行器(例如,没有 CompletableFuture.supplyAsync
),以确保 Quarkus 可以传播它。有关更多信息,请参阅 Context Propagation Guide。
This will allow you to propagate the identity throughout the reactive callbacks. You also need to make sure you
are using an executor that is capable of propagating the identity (e.g. no CompletableFuture.supplyAsync
),
to make sure that Quarkus can propagate it. For more information see the
Context Propagation Guide.
Observe security events
Quarkus bean 可以使用 CDI observers 来使用身份验证和授权安全事件。观察者可以是同步的或异步的。
Quarkus beans can use CDI observers to consume authentication and authorization security events. The observers can be either synchronous or asynchronous.
-
io.quarkus.security.spi.runtime.AuthenticationFailureEvent
-
io.quarkus.security.spi.runtime.AuthenticationSuccessEvent
-
io.quarkus.security.spi.runtime.AuthorizationFailureEvent
-
io.quarkus.security.spi.runtime.AuthorizationSuccessEvent
-
io.quarkus.oidc.SecurityEvent
-
io.quarkus.vertx.http.runtime.security.FormAuthenticationEvent
有关 Quarkus OpenID Connect 扩展的特定安全事件的更多信息,请参阅 OIDC 代码流机制的 Listening to important authentication events 部分,用于保护 Web 应用程序指南。 |
For more information about security events specific to the Quarkus OpenID Connect extension, please see the Listening to important authentication events section of the OIDC code flow mechanism for protecting web applications guide. |
package org.acme.security;
import io.quarkus.security.spi.runtime.AuthenticationFailureEvent;
import io.quarkus.security.spi.runtime.AuthenticationSuccessEvent;
import io.quarkus.security.spi.runtime.AuthorizationFailureEvent;
import io.quarkus.security.spi.runtime.AuthorizationSuccessEvent;
import io.quarkus.security.spi.runtime.SecurityEvent;
import io.vertx.ext.web.RoutingContext;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.event.ObservesAsync;
import org.jboss.logging.Logger;
public class SecurityEventObserver {
private static final Logger LOG = Logger.getLogger(SecurityEventObserver.class.getName());
void observeAuthenticationSuccess(@ObservesAsync AuthenticationSuccessEvent event) { 1
LOG.debugf("User '%s' has authenticated successfully", event.getSecurityIdentity().getPrincipal().getName());
}
void observeAuthenticationFailure(@ObservesAsync AuthenticationFailureEvent event) {
RoutingContext routingContext = (RoutingContext) event.getEventProperties().get(RoutingContext.class.getName());
LOG.debugf("Authentication failed, request path: '%s'", routingContext.request().path());
}
void observeAuthorizationSuccess(@ObservesAsync AuthorizationSuccessEvent event) {
String principalName = getPrincipalName(event);
if (principalName != null) {
LOG.debugf("User '%s' has been authorized successfully", principalName);
}
}
void observeAuthorizationFailure(@Observes AuthorizationFailureEvent event) {
LOG.debugf(event.getAuthorizationFailure(), "User '%s' authorization failed", event.getSecurityIdentity().getPrincipal().getName());
}
private static String getPrincipalName(SecurityEvent event) { 2
if (event.getSecurityIdentity() != null) {
return event.getSecurityIdentity().getPrincipal().getName();
}
return null;
}
}
1 | This observer consumes all the AuthenticationSuccessEvent events asynchronously, which means that HTTP request processing will continue regardless on the event processing.
Depending on the application, that can be a lot of the AuthenticationSuccessEvent events.
For that reason, asynchronous processing can have positive effect on performance. |
2 | Common code for all supported security event types is possible because they all implement the io.quarkus.security.spi.runtime.SecurityEvent interface. |