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:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security</artifactId>
</dependency>
build.gradle
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();
	}

}

HttpAuthenticationMechanism 应将包含适合身份验证凭据的传入 HTTP 请求转换为 io.quarkus.security.identity.request.AuthenticationRequest 实例,并将身份验证委托给 io.quarkus.security.identity.IdentityProviderManager。在简单的情况下,可以省略对 io.quarkus.security.identity.IdentityProvider`s gives you more options for credentials verifications, as well as convenient way to perform blocking tasks. Nevertheless, the `io.quarkus.security.identity.IdentityProvider 的身份验证,HttpAuthenticationMechanism 可以自行对请求进行身份验证。

The HttpAuthenticationMechanism should transform incoming HTTP request with suitable authentication credentials into an io.quarkus.security.identity.request.AuthenticationRequest instance and delegate the authentication to the io.quarkus.security.identity.IdentityProviderManager. Leaving authentication to the io.quarkus.security.identity.IdentityProvider`s gives you more options for credentials verifications, as well as convenient way to perform blocking tasks. Nevertheless, the `io.quarkus.security.identity.IdentityProvider can be omitted and the HttpAuthenticationMechanism is free to authenticate request on its own in trivial use cases.

Dealing with more than one HttpAuthenticationMechanism

可以组合多个 HttpAuthenticationMechanism,例如,必须使用 Quarkus 中提供的内置 BasicJWT 机制来验证作为 HTTP Authorization BasicBearer 架构值传递的服务客户端凭据,而必须使用 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,紧跟其后的是优先级为 1001Authorization 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 状态由 BasicJWT 机制返回,重定向用户至 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.

因此,如果 BasicAuthorization Code 机制相结合,则如果没有提供凭据,则返回 401,如果 JWTAuthorization 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();
        }
    }
}

如果注册了多个自定义 SecurityIdentityAugmentor,则它们将被视为同等候选对象并按随机顺序调用。你可以通过实现一个默认 SecurityIdentityAugmentor#priority 方法来强制执行此顺序。将优先调用具有更高优先级的增强程序。

If more than one custom SecurityIdentityAugmentor is registered then they will be considered equal candidates and invoked in random order. You can enforce the order by implementing a default SecurityIdentityAugmentor#priority method. Augmentors with higher priorities will be invoked first.

默认情况下,在增强安全身份时不会激活请求上下文,这意味着如果你想使用例如强制使用请求上下文的 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.

如果你实施了一个自定的 HttpAuthenticationMechanism,则需要使用 io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.setRoutingContextAttribute 方法调用将 RoutingContext 添加到身份验证请求属性。否则,在增强期间 RoutingContext 将不可用。

If you implemented a custom HttpAuthenticationMechanism, then you need to add the RoutingContext to the authentication request attributes with the io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.setRoutingContextAttribute method call. Otherwise, the RoutingContext will not be available during augmentation.

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:

Example Security Providers Configuration
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:

Example Security Providers BouncyCastle Configuration
quarkus.security.security-providers=BC

并添加 BouncyCastle 提供程序依赖项:

and add the BouncyCastle provider dependency:

pom.xml
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
</dependency>
build.gradle
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:

Example Security Providers BouncyCastle JSSE Configuration
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:

pom.xml
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bctls-jdk18on</artifactId>
</dependency>
build.gradle
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:

Example Security Providers BouncyCastle FIPS Configuration
quarkus.security.security-providers=BCFIPS

并添加 BouncyCastle FIPS 提供程序依赖项:

and add the BouncyCastle FIPS provider dependency:

pom.xml
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bc-fips</artifactId>
</dependency>
build.gradle
implementation("org.bouncycastle:bc-fips")

BCFIPS 提供程序选项在本地映像中受支持,但算法自测依赖于 java.security.SecureRandom 来验证生成的密钥,这些测试已删除以便通过这些测试。以下类已受到影响:- org.bouncycastle.crypto.general.DSA- org.bouncycastle.crypto.general.DSTU4145- org.bouncycastle.crypto.general.ECGOST3410- org.bouncycastle.crypto.general.GOST3410- org.bouncycastle.crypto.fips.FipsDSA- org.bouncycastle.crypto.fips.FipsEC- org.bouncycastle.crypto.fips.FipsRSA

BCFIPS provider option is supported in native image but the algorithm self-tests which rely on java.security.SecureRandom to verify the generated keys have been removed for these tests to pass. The following classes have been affected: - org.bouncycastle.crypto.general.DSA - org.bouncycastle.crypto.general.DSTU4145 - org.bouncycastle.crypto.general.ECGOST3410 - org.bouncycastle.crypto.general.GOST3410 - org.bouncycastle.crypto.fips.FipsDSA - org.bouncycastle.crypto.fips.FipsEC - org.bouncycastle.crypto.fips.FipsRSA

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:

Example Security Providers BouncyCastle FIPS JSSE Configuration
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:

pom.xml
<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bctls-fips</artifactId>
</dependency>

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bc-fips</artifactId>
</dependency>
build.gradle
implementation("org.bouncycastle:bctls-fips")
implementation("org.bouncycastle:bc-fips")

请注意,密钥库和信任库类型及提供程序已设置为 BCFKSBCFIPS。可以像这样使用此类型和提供程序生成密钥库:

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

BCFIPSJSSE 提供程序选项当前在本地映像中不受支持。

BCFIPSJSSE provider option is currently not supported in native image.

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

请注意,虽然在本地镜像中支持访问 SunPKCS11 桥接提供程序,但当前在 Quarkus 级别不支持在本地镜像中配置 SunPKCS11

Note that while accessing the SunPKCS11 bridge provider is supported in native image, configuring SunPKCS11 is currently not supported in native image at the Quarkus level.

Reactive Security

如果您打算在响应式环境中使用安全性,则可能需要 SmallRye Context Propagation:

If you are going to use security in a reactive environment, you will likely need SmallRye Context Propagation:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
build.gradle
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.

List of supported security events
  • 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.