The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
このページを編集

セキュリティに関するヒントとコツ

Quarkus Security 依存関係

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

ほとんどの場合、すべてのセキュリティーエクステンションですでに提供されているため、プロジェクトのビルドファイルに直接追加する必要はありません。しかし、独自のカスタムセキュリティーコードを書いたり(例えば、Custom Jakarta REST SecurityContext を登録したり)、BouncyCastle ライブラリーを使用する必要がある場合は、必ず含めてください。

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

}
HttpAuthenticationMechanism は、適切な認証クレデンシャルを持つ受信 HTTP リクエストを io.quarkus.security.identity.request.AuthenticationRequest インスタンスに変換し、認証を io.quarkus.security.identity.IdentityProviderManager に委譲する必要があります。認証を io.quarkus.security.identity.IdentityProvider に委ねることで、クレデンシャル検証のオプションが増えるだけでなく、ブロッキングタスクを実行する便利な方法が得られます。それにもかかわらず、io.quarkus.security.identity.IdentityProvider を省略することもでき、HttpAuthenticationMechanism は些細なユースケースではそれ自身でリクエストを認証できます。

複数の HttpAuthenticationMechanism を処理する

複数の HttpAuthenticationMechanism を組み合わせることができます。例えば、HTTP Authorization Basic または Bearer スキーム値として渡されるサービス クライアントのクレデンシャルを検証するには、 quarkus-smallrye-jwt が提供する組み込みの Basic または JWT 認証メカニズムを使用する必要があります。一方、Keycloak やその他の OpenID Connect プロバイダーでユーザーを認証するには、 quarkus-oidc が提供する 認可コード 認証メカニズムを使用する必要があります。

このような場合、 SecurityIdentity が作成されるまで、メカニズムが順番にクレデンシャルを検証するように要求されます。認証メカニズムは、優先度の降順でソートされています。 Basic 認証メカニズムの優先順位は最高で 2000 、次いで 認可コード の優先順位は 1001 、Quarkus が提供する他のすべてのメカニズムの優先順位は 1000 となっています。

クレデンシャルが提供されない場合、メカニズム固有のチャレンジが作成されます。例えば、Basic または JWT メカニズムのいずれかによって 401 ステータスが返されたり、quarkus-oidc によってユーザーを OpenID Connect プロバイダーにリダイレクトする URL が返されたりします。

したがって、 Basic認可コード のメカニズムが組み合わされた場合、クレデンシャルが提供されなければ 401 が返され、 JWT認可コード のメカニズムが組み合わされた場合、リダイレクト URL が返されます。

場合によっては、このようなデフォルトのチャレンジ選択ロジックが特定のアプリケーションでまさに要求されるものですが、要件を満たさないこともあります。そのような場合(または、メカニズムが現在の認証またはチャレンジリクエストを処理するように求められる順序を変更したい他の同様の場合)、カスタムメカニズムを作成し、どのメカニズムがチャレンジを作成すべきかを選択できます。例えば、次のとおりです。

@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 メカニズムを代替 Bean として宣言することで、OidcAuthenticationMechanismJWTAuthMechanism ではなく、このメカニズムが使用されることが保証されます。

Security Identity のカスタマイズ

内部的には、アイデンティティープロバイダーは、プリンシパル、ロール、クライアント(ユーザー)の認証に使用されたクレデンシャル、およびその他のセキュリティー属性を保持する 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 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)); (1)
    }

    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;
        }
    }
}
1 セキュリティーアイデンティティーをオーグメントします。ブロッキングモードでアイデンティティーをオーグメントする方法の例については、リクエストコンテキストのアクティブ化 セクションを参照してください。

現在の 相互 TLS (mTLS) 認証 リクエストで利用可能なクライアント証明書を使用して、さらにロールを追加する方法を示す別の例を以下に示します。

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 メソッドを実装することで、順序を強制できます。優先度の高いオーグメンターが最初に呼び出されます。

リクエストコンテキストのアクティブ化

デフォルトでは、セキュリティーアイデンティティーをオーグメントする際、リクエストコンテキストはアクティブ化されません。これは、例えばリクエストコンテキストを必須とする Hibernate を使用したい場合、jakarta.enterprise.context.ContextNotActiveException が発生することを意味します。

「プロアクティブ認証」ガイドの CDI リクエストコンテキストのアクティブ化 セクションも参照してください。

解決策はリクエストコンテキストをアクティブにすることです。次の例は、Panache を使用した Hibernate UserRoleEntity からロールを取得する方法を示しています。

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.smallrye.mutiny.Uni;

@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {

    @Inject
    UserEntityAugmentor userEntityAugmentor;

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

        // Hibernate ORM is blocking
        return context.runBlocking(() -> userEntityAugmentor.augment(identity));
    }
}
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.control.ActivateRequestContext;

import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
class UserEntityAugmentor {

    @ActivateRequestContext
    public SecurityIdentity augment(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();
    }
}

プロアクティブ認証が有効な場合、上記の例で示された CDI リクエストコンテキストのアクティブ化は、RoutingContext へのアクセスには役立ちません。次の例は、SecurityIdentityAugmentor から RoutingContext にアクセスする方法を示しています。

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 HTTP リクエストが完了した後に SecurityIdentity がオーグメントされる場合、RoutingContext は利用できません。
カスタム HttpAuthenticationMechanism を実装した場合、io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.setRoutingContextAttribute メソッド呼び出しを使用して、RoutingContext を認証リクエスト属性に追加する必要があります。そうしないと、オーグメンテーション中に RoutingContext は利用できません。

カスタム Jakarta REST SecurityContext

Jakarta REST ContainerRequestFilter を使用してカスタム Jakarta REST SecurityContext を設定する場合、このカスタムセキュリティーコンテキストが Quarkus SecurityIdentity とリンクされるように、@PreMatching アノテーションを追加するなどして、ContainerRequestFilter が Jakarta REST の事前一致フェーズで実行されるようにしてください。例えば次のとおりです。

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

    }
}

認可の無効化

認可を無効にする正当な理由がある場合は、カスタム 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 とまったく同じ効果を持ちますが、開発モードでのみ利用可能です。

quarkus.security.auth.enabled-in-dev-mode=false

TestSecurity アノテーションを使用してセキュリティーチェックを無効にする方法については、TestingSecurity アノテーション セクションも参照してください。

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

デフォルトプロバイダ

ネイティブモードで実行する場合、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-jdk18on</artifactId>
</dependency>
build.gradle
implementation("org.bouncycastle:bcprov-jdk18on")

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-jdk18on</artifactId>
</dependency>
build.gradle
implementation("org.bouncycastle:bctls-jdk18on")

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 プロバイダー・オプションはネイティブ・イメージでサポートされていますが、生成されたキーを検証するために 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

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

キーストアとトラストストアのタイプおよびプロバイダーが 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 プロバイダーオプションは、現在ネイティブイメージではサポートされていません。

SunPKCS11

SunPKCS11 プロバイダーは、暗号化スマートカードやその他のハードウェアセキュリティモジュール、FIPSモードのネットワークセキュリティサービスなど、特定の PKCS#11 実装へのブリッジを提供します。

通常、 SunPKCS11 を扱うには、 PKCS#11 の実装をインストールし、共有ライブラリ、トークンスロットなどを参照する設定を生成し、以下のJavaコードを記述する必要があります。

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では、例えばコードを修正することなく、設定レベルのみで同じことを実現することができます。

quarkus.security.security-providers=SunPKCS11
quarkus.security.security-provider-config.SunPKCS11=pkcs11.cfg

SunPKCS11 ブリッジプロバイダへのアクセスはネイティブイメージでサポートされていますが、 SunPKCS11 の設定は現在 Quarkus レベルでネイティブイメージでサポートされていないことに注意してください。

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

これにより、リアクティブコールバック全体にアイデンティティを伝搬させることができます。また、Quarkus がアイデンティティを伝搬できるように、アイデンティティを伝搬できるエグゼキューター (例: CompletableFuture.supplyAsync を使用しない) を使用していることを確認する必要があります。詳細については、Context Propagation Guide を参照してください。

セキュリティイベントの監視

Quarkus の Bean は、CDI オブザーバー を使用して、認証および認可のセキュリティイベントを消費できます。オブザーバーは同期または非同期のいずれかになります。

サポートされているセキュリティイベントのリスト
  • 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 エクステンションに固有のセキュリティイベントの詳細については、Webアプリケーションを保護するための OIDC 認可コードフローメカニズムガイドの 重要な認証イベントのリスニング セクションを参照してください。
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 このオブザーバーはすべての AuthenticationSuccessEvent イベントを非同期に消費します。つまり、HTTP リクエスト処理はイベント処理に関係なく継続されます。アプリケーションによっては、 AuthenticationSuccessEvent イベントは大量になります。そのため、非同期処理はパフォーマンスに良い影響を与えます。
2 すべてのサポートされているセキュリティイベントタイプは、io.quarkus.security.spi.runtime.SecurityEvent インターフェースをすべて実装しているため、共通のコードで処理できます。

SecurityIdentity の Vert.x ルートハンドラーへのインジェクション

Vert.x Web Router に直接登録された Vert.x ルートハンドラーに SecurityIdentity をインジェクトする必要がある場合、 SecurityIdentity の伝播を要求する必要があります。例:

package io.quarkus.it.security;

public class RouterObserver {

    public void route(@Observes Router router, UserInformation userInformation) {
        router.route("/user-info").handler(event -> event.response().end(userInformation.getPrincipalName()));
    }

}

例の UserInformation Bean は次のようになります:

package io.quarkus.it.security;

@ApplicationScoped
public class UserInformation {

    @Inject
    SecurityIdentity identity;

    @ActivateRequestContext
    String getPrincipalName() {
        return identity.getPrincipal().getName();
    }

}

この例で SecurityIdentity のインジェクションが機能するには、 SecurityIdentity の伝播をアクティブにする必要があります:

quarkus.http.auth.propagate-security-identity=true (1)
1 Vert.x HTTP ルーターに Vert.x ルートハンドラーがプログラムによって登録されるときに、SecurityIdentity を伝播します。これは、Quarkus がプログラムによって登録された Vert.x ルートハンドラーで SecurityIdentity のインジェクションポイントが使用されていることを検出できないためです。@Route アノテーションで宣言的に登録されたルートや Jakarta REST エンドポイントの場合、この設定は必要ありません。

関連コンテンツ