The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Security Tips and Tricks

Quarkus Security依存関係

io.quarkus:quarkus-security モジュールには、Quarkusの中核となるセキュリティクラスが含まれています。

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 Custom JAX-RS SecurityContext) or use BouncyCastle 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 のカスタマイズ

CDI実装Beanを登録することで、 HttpAuthenticationMechanism をカスタマイズすることができます。以下の例では、カスタム認証機能は quarkus-smallrye-jwt によって提供される JWTAuthMechanism にデリゲートします。

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

}

Dealing with more than one HttpAuthenticationMechanism

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.

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.

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.

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:

@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() {
		return selectBetweenJwtAndOidc(context).getCredentialTypes();
	}

	@Override
	public HttpCredentialTransport getCredentialTransport(RoutingContext context) {
		return selectBetweenJwtAndOidc(context).getCredentialTransport();
	}

        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
        }

}

セキュリティー・アイデンティティのカスタマイズ

内部的には、アイデンティティ・プロバイダは、プリンシパル、役割、クライアント(ユーザー)の認証に使用されたクレデンシャル、 およびその他のセキュリティー属性を保持する io.quarkus.security.identity.SecurityIdentity クラスのインスタンスを作成して更新します。 SecurityIdentity をカスタマイズする簡単なオプションは、カスタム SecurityIdentityAugmentor を登録することです。例えば、以下のエクステンションは、追加のロールを追加します。

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 javax.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;
        }
    }
}

Here is another example showing how to use the client certificate available in the current Mutual TLS 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 javax.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 メソッドを実装することで順番を強制することができます。優先度の高いAugmentors が最初に起動されます。

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 javax.enterprise.context.ContextNotActiveException.

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.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.control.ActivateRequestContext;
import java.util.function.Supplier;

@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
    @Override
    public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
        if(identity.isAnonymous()) {
            return Uni.createFrom().item(identity);
        }

        // Hibernate ORM is blocking
        return context.runBlocking(build(identity));
    }

    @ActivateRequestContext // Will activate the request context
    Supplier<SecurityIdentity> build(SecurityIdentity identity) {
        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;
    }
}

カスタム JAX-RS SecurityContext

JAX-RS ContainerRequestFilter を使用してカスタム JAX-RS SecurityContext を設定する場合は、 ContainerRequestFilter が JAX-RS のプレマッチフェーズで実行されていることを確認し、このカスタムセキュリティーコンテキストが Quarkus SecurityIdentity とリンクされるように @PreMatching アノテーションを追加します。例:

import java.security.Principal;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.SecurityContext;
import javax.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";
                }
            });
        }

    }
}

認可の無効化

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

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

Please also see TestingSecurity Annotation section on how to disable the security checks using TestSecurity annotation.

セキュリティー プロバイダの登録

デフォルトのプロバイダ

ネイティブモードで実行している場合、GraalVMネイティブ実行可能ファイル生成のデフォルトの動作は、SSLを有効にしていない限り、メインの「SUN」プロバイダのみを含めるようになっています。SSLを有効にしている場合は、すべてのセキュリティープロバイダが登録されます。SSLを使用していない場合は、 quarkus.security.security-providers プロパティーを使用して、セキュリティープロバイダを名前で選択的に登録することができます。以下の例では、「SunRsaSign」と「SunJCE」のセキュリティープロバイダを登録するための設定を説明します。

セキュリティー プロバイダの設定例
quarkus.security.security-providers=SunRsaSign,SunJCE

BouncyCastle

org.bouncycastle.jce.provider.BouncyCastleProvider JCE プロバイダを登録する必要がある場合は、 BC プロバイダ名を設定してください。

セキュリティープロバイダの例 BouncyCastleの設定
quarkus.security.security-providers=BC

そして、BouncyCastleプロバイダ依存関係を追加します。

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

BouncyCastle JSSE

org.bouncycastle.jsse.provider.BouncyCastleJsseProvider JSSE プロバイダを登録して、デフォルトの SunJSSE プロバイダの代わりに使用する必要がある場合は、 BCJSSE プロバイダ名を設定してください。

セキュリティープロバイダの例 BouncyCastle JSSE設定
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依存関係を追加します。

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

BouncyCastle FIPS

org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider JCE プロバイダを登録する必要がある場合は、 BCFIPS プロバイダ名を設定してください。

セキュリティープロバイダの例 BouncyCastleのFIPS設定
quarkus.security.security-providers=BCFIPS

そして、BouncyCastle FIPSプロバイダの依存関係を追加します。

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

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

デフォルトのSunJSSEプロバイダではなく、 org.bouncycastle.jsse.provider.BouncyCastleJsseProvider JSSEプロバイダを登録して、 org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider と組み合わせて使用する必要がある場合は、 BCFIPSJSSE プロバイダ名を設定してください。

セキュリティープロバイダの例 BouncyCastle FIPS JSSE設定
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依存関係:

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")

keystore と truststore のタイプとプロバイダが BCFKSBCFIPS に設定されていることに注意してください。このタイプとプロバイダでは、以下のようにキーストアを生成することができます。

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 provider option is currently not supported in native image.

SunPKCS11

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.

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

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 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が必要になるでしょう。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-smallrye-context-propagation")

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.