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

OpenID Connect (OIDC) ベアラートークン認証

Quarkus OpenID Connect(OIDC)エクステンションを使用することで、ベアラートークン認証を使って、アプリケーション内のJakarta REST(旧称JAX-RS)エンドポイントへのHTTPアクセスを保護できます。

Quarkusのベアラートークン認証メカニズムの概要

Quarkusは、Quarkus OpenID Connect(OIDC)エクステンションを通じて、ベアラートークン認証メカニズムをサポートしています。

ベアラートークンは、 Keycloak などのOIDCやOAuth2.0に準拠した認可サーバーで発行されます。

ベアラートークン認証は、ベアラートークンの存在と有効性に基づいて HTTP リクエストを認可するプロセスです。 ベアラートークンは、呼び出しの主体に関する情報を提供します。この情報は、HTTP リソースにアクセスできるかどうかを判断するために使用されます。

以下の図に、Quarkusのベアラートークン認証メカニズムの概要を示します:

Bearer token authentication
Figure 1. Quarkus のベアラー認証メカニズムとシングルページアプリケーション
  1. Quarkus サービスが、OIDC プロバイダーから検証キーを取得します。 検証キーは、ベアラーアクセストークンの署名を検証するために使用されます。

  2. Quarkus ユーザーが、シングルページアプリケーション (SPA) にアクセスします。

  3. シングルページアプリケーションが、認可コードフローを使用してユーザーを認証し、OIDC プロバイダーからトークンを取得します。

  4. シングルページアプリケーションが、アクセストークンを使用して、Quarkus サービスからサービスデータを取得します。

  5. Quarkus サービスが、検証キーを使用してベアラーアクセストークンの署名を検証し、トークンの有効期限やその他のクレームをチェックします。トークンが有効であればリクエストの続行を許可し、シングルページアプリケーションにサービスレスポンスを返します。

  6. シングルページアプリケーションが、Quarkus ユーザーに同じデータを返します。

Bearer token authentication
Figure 2. Quarkus のベアラートークン認証メカニズムとJava またはコマンドラインクライアント
  1. Quarkus サービスが、OIDC プロバイダーから検証キーを取得します。 検証キーは、ベアラーアクセストークンの署名を検証するために使用されます。

  2. クライアントが、client_credentials かパスワードグラントを使用して、OIDC プロバイダーからアクセストークンを取得します。client_credentials には、クライアント ID とシークレットが必要です。パスワードグラントには、クライアント ID、シークレット、ユーザー名、およびパスワードが必要です。

  3. クライアントがアクセストークンを使用して、Quarkus サービスからサービスデータを取得します。

  4. Quarkus サービスが、検証キーを使用してベアラーアクセストークンの署名を検証し、トークンの有効期限やその他のクレームをチェックします。トークンが有効であればリクエストの続行を許可し、サービスレスポンスをクライアントに返します。

OIDC 認可コードフローを使用してユーザーを認証および認可する必要がある場合は、Quarkus の Web アプリケーションを保護するための OpenID Connect 認可コードフローメカニズム ガイドを参照してください。 また、Keycloak とベアラートークンを使用する場合は、Quarkus Keycloak を使用した認可の一元化 ガイドを参照してください。

To learn about how you can protect service applications by using OIDC Bearer token authentication, see the following tutorial:

マルチテナントをサポートする方法については、Quarkus OpenID Connect マルチテナンシーの使用 ガイドを参照してください。

JWT クレームへのアクセス

JWT トークンクレームにアクセスする必要がある場合は、JsonWebToken を注入できます。

package org.acme.security.openid.connect;

import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.inject.Inject;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/api/admin")
public class AdminResource {

    @Inject
    JsonWebToken jwt;

    @GET
    @RolesAllowed("admin")
    @Produces(MediaType.TEXT_PLAIN)
    public String admin() {
        return "Access for subject " + jwt.getSubject() + " is granted";
    }
}

JsonWebToken の注入は、@ApplicationScoped@Singleton、および @RequestScoped スコープでサポートされています。 ただし、個々のクレームが単純型として挿入される場合は、@RequestScoped を使用する必要があります。 詳細は、Quarkus の「JWT RBAC の使用」ガイドの サポートされている注入スコープ セクションを参照してください。

UserInfo

OIDC UserInfo エンドポイントから UserInfo JSON オブジェクトをリクエストする必要がある場合は、quarkus.oidc.authentication.user-info-required=true を設定します。 OIDC プロバイダーの UserInfo エンドポイントにリクエストが送信され、io.quarkus.oidc.UserInfo (単純な javax.json.JsonObject ラッパー) オブジェクトが作成されます。 io.quarkus.oidc.UserInfo は、SecurityIdentity userinfo 属性として注入またはアクセスできます。

quarkus.oidc.authentication.user-info-required is automatically enabled if one of these conditions is met:

  • if quarkus.oidc.roles.source is set to userinfo or quarkus.oidc.token.verify-access-token-with-user-info is set to true or quarkus.oidc.authentication.id-token-required is set to false, the current OIDC tenant must support a UserInfo endpoint in these cases.

  • if io.quarkus.oidc.UserInfo injection point is detected but only if the current OIDC tenant supports a UserInfo endpoint.

設定メタデータ

現在のテナントの検出された OpenID Connect 設定メタデータ は、io.quarkus.oidc.OidcConfigurationMetadata で表され、SecurityIdentity configuration-metadata 属性として注入またはアクセスできます。

エンドポイントがパブリックの場合、デフォルトのテナントの OidcConfigurationMetadata が注入されます。

トークンクレームと SecurityIdentity ロール

SecurityIdentity ロールは、次のように、検証済みの JWT アクセストークンからマッピングすることができます。

  • quarkus.oidc.roles.role-claim-path プロパティーが設定されており、一致する配列または文字列のクレームが見つかった場合、そのクレームからロールが抽出されます。 たとえば、customrolescustomroles/arrayscope"http://namespace-qualified-custom-claim"/roles"http://namespace-qualified-roles" などです。

  • groups クレームが利用可能な場合は、その値が使用されます。

  • realm_access/roles または resource_access/client_id/roles (client_idquarkus.oidc.client-id プロパティーの値) クレームが利用可能な場合は、その値が使用されます。 このチェックは、Keycloak が発行するトークンをサポートします。

たとえば、次の JWT トークンには、ロールが含まれている roles 配列を含む複雑な groups クレームがあります。

{
    "iss": "https://server.example.com",
    "sub": "24400320",
    "upn": "jdoe@example.com",
    "preferred_username": "jdoe",
    "exp": 1311281970,
    "iat": 1311280970,
    "groups": {
        "roles": [
          "microprofile_jwt_user"
        ],
    }
}

microprofile_jwt_user ロールは、SecurityIdentity ロールにマップする必要があります。これは、quarkus.oidc.roles.role-claim-path=groups/roles 設定で実行できます。

トークンが不透明 (バイナリー) の場合は、リモートトークンイントロスペクションレスポンスの scope プロパティーが使用されます。

UserInfo がロールのソースである場合は、quarkus.oidc.authentication.user-info-required=true および quarkus.oidc.roles.source=userinfo を設定し、必要に応じて quarkus.oidc.roles.role-claim-path を設定します。

さらに、カスタムの SecurityIdentityAugmentor を使用してロールを追加することもできます。 詳細は、Quarkus の「セキュリティーに関するヒントとコツ」ガイドの セキュリティーアイデンティティーのカスタマイズ セクションを参照してください。

また、HTTP セキュリティーポリシー を使用して、トークンクレームから作成された SecurityIdentity ロールをデプロイメント固有のロールにマップすることもできます。

トークンのスコープと SecurityIdentity の権限

SecurityIdentity 権限は、io.quarkus.security.StringPermission の形式で、source of the roles のスコープパラメーターから、同じクレームセパレーターを使用してマッピングされます。

import java.util.List;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;

import io.quarkus.security.PermissionsAllowed;

@Path("/service")
public class ProtectedResource {

    @Inject
    JsonWebToken accessToken;

    @PermissionsAllowed("email") (1)
    @GET
    @Path("/email")
    public Boolean isUserEmailAddressVerifiedByUser() {
        return accessToken.getClaim(Claims.email_verified.name());
    }

    @PermissionsAllowed("orders_read") (2)
    @GET
    @Path("/order")
    public List<Order> listOrders() {
        return List.of(new Order("1"));
    }

    public static class Order {
        String id;
        public Order() {
        }
        public Order(String id) {
            this.id = id;
        }
        public String getId() {
            return id;
        }
        public void setId() {
            this.id = id;
        }
    }
}
1 OpenID Connect スコープ email を持つリクエストのみにアクセスが許可されます。
2 読み取りアクセスは、orders_read スコープを持つクライアントリクエストに制限されます。

io.quarkus.security.PermissionsAllowed アノテーションの詳細は、「Web エンドポイントの認可」ガイドの 権限アノテーション セクションを参照してください。

トークンの検証とイントロスペクション

トークンが JWT トークンの場合、デフォルトでは、OIDC プロバイダーの JWK エンドポイントから取得されたローカルの JsonWebKeySetJsonWebKey (JWK) キーを使用してトークンが検証されます。 トークンのキー識別子 (kid) ヘッダー値は、一致する JWK キーを検出するために使用されます。 一致する JWK がローカルに存在しない場合は、JWK エンドポイントから現在のキーセットが取得され、JsonWebKeySet が更新されます。 JsonWebKeySet の更新は、quarkus.oidc.token.forced-jwk-refresh-interval の有効期限が切れた後にのみ繰り返すことができます。 デフォルトの有効期限は 10 分です。 一致する JWK が更新後に利用できない場合は、JWT トークンが OIDC プロバイダーのトークンイントロスペクションエンドポイントに送信されます。

トークンが不透明である場合、つまりバイナリートークンまたは暗号化された JWT トークンである場合、そのトークンは常に OIDC プロバイダーのトークンイントロスペクションエンドポイントに送信されます。

JWT トークンのみを使用しており、一致する JsonWebKey が常に使用可能であることが予想される場合 (たとえば、キーセットを更新した後)、次の例に示すように、トークンイントロスペクションを無効にする必要があります。

quarkus.oidc.token.allow-jwt-introspection=false
quarkus.oidc.token.allow-opaque-token-introspection=false

場合によっては、JWT トークンをイントロスペクションのみで検証しなければならないことがあります。これは、イントロスペクションエンドポイントアドレスのみを設定することで強制できます。 次のプロパティー設定は、これを Keycloak を使用して実現する方法の例を示しています。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.discovery-enabled=false
# Token Introspection endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/tokens/introspect
quarkus.oidc.introspection-path=/protocol/openid-connect/tokens/introspect

JWT トークンのイントロスペクションをリモートで実施することを間接的に強制することには、利点と欠点があります。 利点は、2 つのリモート呼び出し (リモート OIDC メタデータディスカバリー呼び出しと、それに続く使用されない検証キーを取得するための別のリモート呼び出し) が不要になることです。 欠点は、ユーザーがイントロスペクションエンドポイントアドレスを確認して、手動で設定する必要があることです。

別の方法としては、デフォルトオプションである OIDC メタデータディスカバリーを許可しながら、リモート JWT イントロスペクションのみを実行するよう要求する方法があります。次の例を参照してください。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.token.require-jwt-introspection-only=true

この方法の利点は、設定がよりシンプルで理解しやすいことです。 欠点は、検証キーが取得されない場合でも、イントロスペクションエンドポイントアドレスを検出するためにリモート OIDC メタデータディスカバリー呼び出しが必要になることです。

io.quarkus.oidc.TokenIntrospection (シンプルな jakarta.json.JsonObject ラッパーオブジェクト) が作成されます。 このオブジェクトは、JWT または不透明トークンどちらかのイントロスペクションが成功した場合、SecurityIdentity introspection 属性として注入またはアクセスできます。

トークンのイントロスペクションと UserInfo キャッシュ

不透明アクセストークンは、すべてリモートでイントロスペクトする必要があります。 場合によっては、JWT アクセストークンをイントロスペクトする必要もあります。 UserInfo も必要な場合は、OIDC プロバイダーへの後続のリモート呼び出しで同じアクセストークンが使用されます。 したがって、UserInfo が必要で、現在のアクセストークンが不透明トークンである場合、そのトークンごとに 2 つのリモート呼び出しが行われます。1 つのリモート呼び出しはトークンをイントロスペクトするためのもので、もう 1 つは UserInfo を取得するためのものです。 トークンが JWT の場合は、トークンをイントロスペクトする必要がなければ、UserInfo を取得するための 1 つのリモート呼び出しだけで済みます。

場合によっては、着信ベアラーまたはコードフローアクセストークンごとに最大 2 つのリモート呼び出しを行うコストが問題になります。

本番環境の場合は、トークンイントロスペクションと UserInfo データを短期間 (たとえば 3 分から 5 分) キャッシュすることを検討してください。

quarkus-oidc は、quarkus.oidc.TokenIntrospectionCache および quarkus.oidc.UserInfoCache インターフェイスを提供します。 これらは @ApplicationScoped キャッシュ実装に使用できます。次の例に示すように、@ApplicationScoped キャッシュ実装を使用して、quarkus.oidc.TokenIntrospection オブジェクトや quarkus.oidc.UserInfo オブジェクトを保存および取得します。

@ApplicationScoped
@Alternative
@Priority(1)
public class CustomIntrospectionUserInfoCache implements TokenIntrospectionCache, UserInfoCache {
...
}

各 OIDC テナントは、quarkus.oidc.TokenIntrospection データ、quarkus.oidc.UserInfo データ、またはその両方の保存を許可または拒否できます。これには、ブール値の quarkus.oidc."tenant".allow-token-introspection-cache プロパティーと quarkus.oidc."tenant".allow-user-info-cache プロパティーを使用します。

さらに、quarkus-oidc は、quarkus.oidc.TokenIntrospectionCachequarkus.oidc.UserInfoCache の両方のインターフェイスを実装する、シンプルなデフォルトのメモリーベースのトークンキャッシュを提供します。

デフォルトの OIDC トークンキャッシュは、次のように設定すると、有効にできます。

# 'max-size' is 0 by default, so the cache can be activated by setting 'max-size' to a positive value:
quarkus.oidc.token-cache.max-size=1000
# 'time-to-live' specifies how long a cache entry can be valid for and will be used by a cleanup timer:
quarkus.oidc.token-cache.time-to-live=3M
# 'clean-up-timer-interval' is not set by default, so the cleanup timer can be activated by setting 'clean-up-timer-interval':
quarkus.oidc.token-cache.clean-up-timer-interval=1M

デフォルトのキャッシュは、トークンをキーとして使用します。各エントリーには、TokenIntrospectionUserInfo、またはその両方が含まれます。 最大で max-size 個のエントリーのみが保持されます。 新しいエントリーを追加するときにキャッシュがすでにいっぱいになっている場合は、期限切れのエントリーを 1 つ削除して領域を確保する試みが行われます。 さらに、クリーンアップタイマーを有効にすると、期限切れのエントリーが定期的にチェックされ、削除されます。

デフォルトのキャッシュ実装を試すことも、カスタムのキャッシュ実装を登録することもできます。

JSON Web トークンのクレーム検証

ベアラーの JWT トークンの署名が検証され、その expires at (exp) クレームがチェックされると、次に iss (issuer) クレーム値が検証されます。

デフォルトでは、iss クレーム値は、既知のプロバイダー設定で検出された issuer プロパティーと比較されます。 ただし、quarkus.oidc.token.issuer プロパティーが設定されている場合は、代わりにそのプロパティーと iss クレーム値が比較されます。

場合によっては、この iss クレーム検証がうまくいかないことがあります。 たとえば、検出された issuer プロパティーには内部の HTTP/IP アドレスが含まれているのに、トークン iss クレーム値には外部の HTTP/IP アドレスが含まれている場合などです。 または、検出された issuer プロパティーにはテンプレートテナント変数が含まれているのに、トークン iss クレーム値には完全なテナント固有の発行者の値が含まれている場合などです。

このような場合は、quarkus.oidc.token.issuer=any を設定して発行者の検証をスキップすることを検討してください。 発行者の検証のスキップは、他の方法を利用できない場合にのみ行ってください。

  • Keycloak を使用していて、ホストアドレスが異なるために発行者検証エラーが発生する場合は、Keycloak に KEYCLOAK_FRONTEND_URL プロパティーを設定して、同じホストアドレスを使用してください。

  • マルチテナントデプロイメントで iss プロパティーがテナント固有のものである場合は、SecurityIdentity tenant-id 属性を使用して、エンドポイントまたはカスタム Jakarta フィルターで発行者が正しいことを確認します。 以下に例を示します。

import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;

import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.OidcConfigurationMetadata;
import io.quarkus.security.identity.SecurityIdentity;

@Provider
public class IssuerValidator implements ContainerRequestFilter {
    @Inject
    OidcConfigurationMetadata configMetadata;

    @Inject JsonWebToken jwt;
    @Inject SecurityIdentity identity;

    public void filter(ContainerRequestContext requestContext) {
        String issuer = configMetadata.getIssuer().replace("{tenant-id}", identity.getAttribute("tenant-id"));
        if (!issuer.equals(jwt.getIssuer())) {
            requestContext.abortWith(Response.status(401).build());
        }
    }
}

トークンの aud (audience) クレーム値を検証するために、quarkus.oidc.token.audience プロパティーを使用することを検討してください。

Jose4j Validator

You can register a custom Jose4j Validator to customize the JWT claim verification process, before org.eclipse.microprofile.jwt.JsonWebToken is initialized. For example:

package org.acme.security.openid.connect;

import static org.eclipse.microprofile.jwt.Claims.iss;

import io.quarkus.arc.Unremovable;
import jakarta.enterprise.context.ApplicationScoped;

import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.JwtContext;
import org.jose4j.jwt.consumer.Validator;

@Unremovable
@ApplicationScoped
public class IssuerValidator implements Validator { (1)

    @Override
    public String validate(JwtContext jwtContext) throws MalformedClaimException {
        if (jwtContext.getJwtClaims().hasClaim(iss.name())
                && "my-issuer".equals(jwtContext.getJwtClaims().getClaimValueAsString(iss.name()))) {
            return "wrong issuer"; (2)
        }
        return null; (3)
    }
}
1 Register Jose4j Validator to verify JWT tokens for all OIDC tenants.
2 Return the claim verification error description.
3 Return null to confirm that this Validator has successfully verified the token.
Use a @quarkus.oidc.TenantFeature annotation to bind a custom Validator to a specific OIDC tenant only.

シングルページアプリケーション

シングルページアプリケーション (SPA) は、通常、XMLHttpRequest (XHR) と OIDC プロバイダーが提供する JavaScript ユーティリティーコードを使用してベアラートークンを取得し、Quarkus service アプリケーションにアクセスします。

たとえば、Keycloak を使用する場合は、keycloak.js を使用してユーザーを認証し、SPA から期限切れのトークンを更新できます。

<html>
<head>
    <title>keycloak-spa</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="http://localhost:8180/js/keycloak.js"></script>
    <script>
        var keycloak = new Keycloak();
        keycloak.init({onLoad: 'login-required'}).success(function () {
            console.log('User is now authenticated.');
        }).error(function () {
            window.location.reload();
        });
        function makeAjaxRequest() {
            axios.get("/api/hello", {
                headers: {
                    'Authorization': 'Bearer ' + keycloak.token
                }
            })
            .then( function (response) {
                console.log("Response: ", response.status);
            }).catch(function (error) {
                console.log('refreshing');
                keycloak.updateToken(5).then(function () {
                    console.log('Token refreshed');
                }).catch(function () {
                    console.log('Failed to refresh token');
                    window.location.reload();
                });
            });
    }
    </script>
</head>
<body>
    <button onclick="makeAjaxRequest()">Request</button>
</body>
</html>

クロスオリジンリソース共有

別のドメインで実行されているシングルページアプリケーションから OIDC service アプリケーションを使用する予定の場合は、クロスオリジンリソース共有 (CORS) を設定する必要があります。 詳細は、「クロスオリジンリソース共有」ガイドの CORS フィルター セクションを参照してください。

プロバイダーエンドポイント設定

OIDC service アプリケーションは、OIDC プロバイダーのトークン、JsonWebKey (JWK) セット、場合によっては UserInfo やイントロスペクションのエンドポイントアドレスを確認する必要があります。

デフォルトでは、これらのエンドポイントアドレスは、設定された quarkus.oidc.auth-server-url/.well-known/openid-configuration パスを追加することで検出されます。

ディスカバリーエンドポイントが利用できない場合や、ディスカバリーエンドポイントのラウンドトリップのコストを節約する場合は、検出を無効にして、エンドポイントアドレスを相対パス値で設定することができます。 以下に例を示します。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.discovery-enabled=false
# Token endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token
quarkus.oidc.token-path=/protocol/openid-connect/token
# JWK set endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/certs
quarkus.oidc.jwks-path=/protocol/openid-connect/certs
# UserInfo endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/userinfo
quarkus.oidc.user-info-path=/protocol/openid-connect/userinfo
# Token Introspection endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/tokens/introspect
quarkus.oidc.introspection-path=/protocol/openid-connect/tokens/introspect

トークンの伝播

For information about bearer access token propagation to the downstream services, see the Token propagation section of the Quarkus "OpenID Connect (OIDC) and OAuth2 client and filters reference" guide.

JWT token certificate chain

In some cases, JWT bearer tokens have an x5c header which represents an X509 certificate chain whose leaf certificate contains a public key that must be used to verify this token’s signature. Before this public key can be accepted to verify the signature, the certificate chain must be validated first. The certificate chain validation involves several steps:

  1. Confirm that every certificate but the root one is signed by the parent certificate.

  2. Confirm the chain’s root certificate is also imported in the truststore.

  3. Validate the chain’s leaf certificate. If a common name of the leaf certificate is configured then a common name of the chain’s leaf certificate must match it. Otherwise the chain’s leaf certificate must also be avaiable in the truststore, unless one or more custom TokenCertificateValidator implementations are registered.

  4. quarkus.oidc.TokenCertificateValidator can be used to add a custom certificate chain validation step. It can be used by all tenants expecting tokens with the certificate chain or bound to specific OIDC tenants with the @quarkus.oidc.TenantFeature annotation.

For example, here is how you can configure Quarkus OIDC to verify the token’s certificate chain, without using quarkus.oidc.TokenCertificateValidator:

quarkus.oidc.certificate-chain.trust-store-file=truststore-rootcert.p12 (1)
quarkus.oidc.certificate-chain.trust-store-password=storepassword
quarkus.oidc.certificate-chain.leaf-certificate-name=www.quarkusio.com (2)
1 The truststore must contain the certificate chain’s root certificate.
2 The certificate chain’s leaf certificate must have a common name equal to www.quarkusio.com. If this property is not configured then the truststore must contain the certificate chain’s leaf certificate unless one or more custom TokenCertificateValidator implementations are registered.

You can add a custom certificate chain validation step by registering a custom quarkus.oidc.TokenCertificateValidator, for example:

package io.quarkus.it.keycloak;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TokenCertificateValidator;
import io.quarkus.oidc.runtime.TrustStoreUtils;
import io.vertx.core.json.JsonObject;

@ApplicationScoped
@Unremovable
public class BearerGlobalTokenChainValidator implements TokenCertificateValidator {

    @Override
    public void validate(OidcTenantConfig oidcConfig, List<X509Certificate> chain, String tokenClaims) throws CertificateException {
        String rootCertificateThumbprint = TrustStoreUtils.calculateThumprint(chain.get(chain.size() - 1));
        JsonObject claims = new JsonObject(tokenClaims);
        if (!rootCertificateThumbprint.equals(claims.getString("root-certificate-thumbprint"))) { (1)
            throw new CertificateException("Invalid root certificate");
        }
    }
}
1 Confirm that the certificate chain’s root certificate is bound to the custom JWT token’s claim.

OIDC プロバイダーのクライアント認証

quarkus.oidc.runtime.OidcProviderClient は、OIDC プロバイダーへのリモートリクエストが必要な場合に使用されます。 ベアラートークンのイントロスペクションが必要な場合は、OidcProviderClient が OIDC プロバイダーに対して認証する必要があります。 サポートされている認証オプションの詳細は、Quarkus の「Web アプリケーションを保護するための OpenID Connect 認可コードフローメカニズム」ガイドの OIDC プロバイダーのクライアント認証 セクションを参照してください。

テスト

Keycloak 認証 を必要とする Quarkus OIDC サービスエンドポイントをテストする必要がある場合は、Keycloak 認証のテスト セクションに従ってください。

次の依存関係をテストプロジェクトに追加することで、テストを開始できます。

pom.xml
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.rest-assured:rest-assured")
testImplementation("io.quarkus:quarkus-junit5")

WireMock

テストプロジェクトに以下の依存関係を追加します。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-oidc-server</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-oidc-server")

REST テストエンドポイントを準備し、application.properties を設定します。 以下に例を示します。

# keycloak.url is set by OidcWiremockTestResource
quarkus.oidc.auth-server-url=${keycloak.url:replaced-by-test-resource}/realms/quarkus/
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.application-type=service

最後にテストコードを記述します。 以下に例を示します。

import static org.hamcrest.Matchers.equalTo;

import java.util.Set;

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
import io.restassured.RestAssured;
import io.smallrye.jwt.build.Jwt;

@QuarkusTest
@QuarkusTestResource(OidcWiremockTestResource.class)
public class BearerTokenAuthorizationTest {

    @Test
    public void testBearerToken() {
        RestAssured.given().auth().oauth2(getAccessToken("alice", Set.of("user")))
            .when().get("/api/users/me")
            .then()
            .statusCode(200)
            // The test endpoint returns the name extracted from the injected `SecurityIdentity` principal.
            .body("userName", equalTo("alice"));
    }

    private String getAccessToken(String userName, Set<String> groups) {
        return Jwt.preferredUserName(userName)
                .groups(groups)
                .issuer("https://server.example.com")
                .audience("https://service.example.com")
                .sign();
    }
}

quarkus-test-oidc-server エクステンションは、JSON Web Key (JWK) フォーマットの署名 RSA 秘密鍵ファイルを含んでおり、smallrye.jwt.sign.key.location 設定プロパティーでそのファイルを参照します。 このエクステンションを使用すると、引数なしの sign() 操作を使用してトークンに署名できます。

OidcWiremockTestResource を使用して quarkus-oidc service アプリケーションをテストすると、通信チャネルも WireMock HTTP スタブに対してテストされるため、最大のカバレッジを得ることができます。 OidcWiremockTestResource でまだサポートされていない WireMock スタブを使用してテストを実行する必要がある場合は、次の例に示すように、テストクラスに WireMockServer インスタンスを挿入できます。

OidcWiremockTestResource は、Docker コンテナーに対する @QuarkusIntegrationTest では機能しません。WireMock サーバーが、テストを実行する JVM で実行されるためです。その JVM には、Quarkus アプリケーションを実行する Docker コンテナーからはアクセスできません。

package io.quarkus.it.keycloak;

import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWireMock;
import io.restassured.RestAssured;

@QuarkusTest
public class CustomOidcWireMockStubTest {

    @OidcWireMock
    WireMockServer wireMockServer;

    @Test
    public void testInvalidBearerToken() {
        wireMockServer.stubFor(WireMock.post("/auth/realms/quarkus/protocol/openid-connect/token/introspect")
                .withRequestBody(matching(".*token=invalid_token.*"))
                .willReturn(WireMock.aResponse().withStatus(400)));

        RestAssured.given().auth().oauth2("invalid_token").when()
                .get("/api/users/me/bearer")
                .then()
                .statusCode(401)
                .header("WWW-Authenticate", equalTo("Bearer"));
    }
}

OidcTestClient

SaaS OIDC プロバイダー (Auth0 など) を使用していて、テスト (開発) ドメインに対してテストを実行したり、リモート Keycloak テストレルムに対してテストを実行したりする場合、quarkus.oidc.auth-server-url がすでに設定されていれば、OidcTestClient を使用できます。

たとえば、次のような設定があるとします。

%test.quarkus.oidc.auth-server-url=https://dev-123456.eu.auth0.com/
%test.quarkus.oidc.client-id=test-auth0-client
%test.quarkus.oidc.credentials.secret=secret

To start, add the same dependency, quarkus-test-oidc-server, as described in the WireMock section.

次に、以下のようにテストコードを記述します。

package org.acme;

import org.junit.jupiter.api.AfterAll;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

import java.util.Map;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.client.OidcTestClient;

@QuarkusTest
public class GreetingResourceTest {

    static OidcTestClient oidcTestClient = new OidcTestClient();

    @AfterAll
    public static void close() {
        oidcTestClient.close();
    }

    @Test
    public void testHelloEndpoint() {
        given()
          .auth().oauth2(getAccessToken("alice", "alice"))
          .when().get("/hello")
          .then()
             .statusCode(200)
             .body(is("Hello, Alice"));
    }

    private String getAccessToken(String name, String secret) {
        return oidcTestClient.getAccessToken(name, secret,
            Map.of("audience", "https://dev-123456.eu.auth0.com/api/v2/",
	           "scope", "profile"));
    }
}

このテストコードは、クライアント ID が test-auth0-client のアプリケーションを登録し、パスワードが alice のユーザー alice を作成したテスト Auth0 ドメインから password グラントを使用してトークンを取得します。 このようなテストを機能させるには、テスト Auth0 アプリケーションで password グラントが有効になっている必要があります。 このサンプルコードでは、追加のパラメーターを渡す方法も示しています。Auth0 の場合、これらは audience および scope パラメーターです。

Dev Services for Keycloak

Keycloak に対する結合テストには、Dev Services for Keycloak を使用することを推奨します。 Dev Services for Keycloak は、テストコンテナーを起動して初期化します。次に、quarkus レルムと quarkus-app クライアント (secret シークレット) を作成し、alice (admin および user ロール) および bob (user ロール) ユーザーを追加します。これらのプロパティーはすべてカスタマイズできます。

まず、次の依存関係を追加します。この依存関係は、アクセストークンを取得するテストで使用できるユーティリティークラス io.quarkus.test.keycloak.client.KeycloakTestClient を提供します。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-keycloak-server</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-keycloak-server")

次に、application.properties 設定ファイルを準備します。 空の application.properties ファイルから開始できます。Dev Services for Keycloakquarkus.oidc.auth-server-url を登録し、その参照先として実行中のテストコンテナー、quarkus.oidc.client-id=quarkus-app、および quarkus.oidc.credentials.secret=secret を設定するためです。

ただし、必要な quarkus-oidc プロパティーをすでに設定している場合は、次の例に示すように、quarkus.oidc.auth-server-urlDev Services for Keycloakprod プロファイルに関連付けるだけで、コンテナーを起動できます。

%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus

テストを実行する前にカスタムレルムファイルを Keycloak にインポートする必要がある場合は、次のように Dev Services for Keycloak を設定します。

%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.keycloak.devservices.realm-path=quarkus-realm.json

最後に、次の例に示すようにテストを記述します。このテストは JVM モードで実行されます。

JVM モードで実行されるテストの例:
package org.acme.security.openid.connect;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;

@QuarkusTest
public class BearerTokenAuthenticationTest {

    KeycloakTestClient keycloakClient = new KeycloakTestClient();

    @Test
    public void testAdminAccess() {
        RestAssured.given().auth().oauth2(getAccessToken("alice"))
                .when().get("/api/admin")
                .then()
                .statusCode(200);
        RestAssured.given().auth().oauth2(getAccessToken("bob"))
                .when().get("/api/admin")
                .then()
                .statusCode(403);
    }

    protected String getAccessToken(String userName) {
        return keycloakClient.getAccessToken(userName);
    }
}
ネイティブモードで実行されるテストの例:
package org.acme.security.openid.connect;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class NativeBearerTokenAuthenticationIT extends BearerTokenAuthenticationTest {
}

Dev Services for Keycloak の初期化と設定の詳細は、Dev Services for Keycloak ガイドを参照してください。

KeycloakTestResourceLifecycleManager

Keycloak との結合テストには KeycloakTestResourceLifecycleManager を使用することもできます。

Use Dev Services for Keycloak instead of KeycloakTestResourceLifecycleManager for integration testing with Keycloak, unless you have specific requirements for using KeycloakTestResourceLifecycleManager.

まず、以下の依存関係を追加します:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-keycloak-server</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-keycloak-server")

これは、Keycloak コンテナーを起動する io.quarkus.test.common.QuarkusTestResourceLifecycleManager の実装である io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager を提供します。

Maven Surefire プラグインを次のように設定します。または、ネイティブイメージテストの maven.failsafe.plugin と同様に設定します。

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <systemPropertyVariables>
            <!-- Or, alternatively, configure 'keycloak.version' -->
            <keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
            <!--
              Disable HTTPS if required:
              <keycloak.use.https>false</keycloak.use.https>
            -->
        </systemPropertyVariables>
    </configuration>
</plugin>

REST テストエンドポイントを準備し、次の例に示すように application.properties を設定します。

# keycloak.url is set by KeycloakTestResourceLifecycleManager
quarkus.oidc.auth-server-url=${keycloak.url:replaced-by-test-resource}/realms/quarkus/
quarkus.oidc.client-id=quarkus-service-app
quarkus.oidc.credentials=secret
quarkus.oidc.application-type=service

最後にテストコードを記述します。 以下に例を示します。

import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
import io.restassured.RestAssured;

@QuarkusTest
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
public class BearerTokenAuthorizationTest {

    @Test
    public void testBearerToken() {
        RestAssured.given().auth().oauth2(getAccessToken("alice"))
            .when().get("/api/users/preferredUserName")
            .then()
            .statusCode(200)
            // The test endpoint returns the name extracted from the injected SecurityIdentity Principal
            .body("userName", equalTo("alice"));
    }

}
まとめ

上記の例では、KeycloakTestResourceLifecycleManageraliceadmin の 2 人のユーザーを登録します。 デフォルトでは、* ユーザー alice には user ロールがあります。このロールは、keycloak.token.user-roles システムプロパティーを使用してカスタマイズできます。 * ユーザー admin には useradmin の両方のロールがあります。これらのロールは、keycloak.token.admin-roles システムプロパティーを使用してカスタマイズできます。

デフォルトでは、KeycloakTestResourceLifecycleManager は HTTPS を使用して Keycloak インスタンスを初期化します。これは keycloak.use.https=false を使用して無効にできます。 デフォルトのレルム名は quarkus で、クライアント ID は quarkus-service-app です。 これらの値をカスタマイズする場合は、keycloak.realm および keycloak.service.client システムプロパティーを設定します。

ローカル公開鍵

次の例に示すように、quarkus-oidc service アプリケーションをテストするために、ローカルのインライン公開鍵を使用できます。

quarkus.oidc.client-id=test
quarkus.oidc.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB

smallrye.jwt.sign.key.location=/privateKey.pem

To generate JWT tokens, copy privateKey.pem from the integration-tests/oidc-tenancy in the main Quarkus repository and use a test code similar to the one in the preceding WireMock section. You can use your own test keys, if preferred.

この方法では、WireMock 方法と比較してカバレッジが制限されます。 たとえば、リモート通信コードはカバーされません。

TestSecurity アノテーション

@TestSecurity および @OidcSecurity アノテーションを使用して、service アプリケーションエンドポイントのコードをテストできます。このコードは、次の注入のいずれか 1 つまたは 3 つすべてに依存します。

  • JsonWebToken

  • UserInfo

  • OidcConfigurationMetadata

まず、以下の依存関係を追加します:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-security-oidc</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-test-security-oidc")

次の例に示すようにテストコードを記述します。

import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.quarkus.test.security.oidc.Claim;
import io.quarkus.test.security.oidc.ConfigMetadata;
import io.quarkus.test.security.oidc.OidcSecurity;
import io.quarkus.test.security.oidc.UserInfo;
import io.restassured.RestAssured;

@QuarkusTest
@TestHTTPEndpoint(ProtectedResource.class)
public class TestSecurityAuthTest {

    @Test
    @TestSecurity(user = "userOidc", roles = "viewer")
    public void testOidc() {
        RestAssured.when().get("test-security-oidc").then()
                .body(is("userOidc:viewer"));
    }

    @Test
    @TestSecurity(user = "userOidc", roles = "viewer")
    @OidcSecurity(claims = {
            @Claim(key = "email", value = "user@gmail.com")
    }, userinfo = {
            @UserInfo(key = "sub", value = "subject")
    }, config = {
            @ConfigMetadata(key = "issuer", value = "issuer")
    })
    public void testOidcWithClaimsUserInfoAndMetadata() {
        RestAssured.when().get("test-security-oidc-claims-userinfo-metadata").then()
                .body(is("userOidc:viewer:user@gmail.com:subject:issuer"));
    }

}

このコード例で使用されている ProtectedResource クラスは次のようになります。

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.quarkus.oidc.OidcConfigurationMetadata;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.Authenticated;

import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("/service")
@Authenticated
public class ProtectedResource {

    @Inject
    JsonWebToken accessToken;
    @Inject
    UserInfo userInfo;
    @Inject
    OidcConfigurationMetadata configMetadata;

    @GET
    @Path("test-security-oidc")
    public String testSecurityOidc() {
        return accessToken.getName() + ":" + accessToken.getGroups().iterator().next();
    }

    @GET
    @Path("test-security-oidc-claims-userinfo-metadata")
    public String testSecurityOidcWithClaimsUserInfoMetadata() {
        return accessToken.getName() + ":" + accessToken.getGroups().iterator().next()
                + ":" + accessToken.getClaim("email")
                + ":" + userInfo.getString("sub")
                + ":" + configMetadata.get("issuer");
    }
}

@TestSecurity アノテーションは常に使用する必要があります。 その user プロパティーは JsonWebToken.getName() として返され、その roles プロパティーは JsonWebToken.getGroups() として返されます。 @OidcSecurity アノテーションは任意です。これを使用すると、追加のトークンクレームと UserInfo および OidcConfigurationMetadata プロパティーを設定できます。 さらに、quarkus.oidc.token.issuer プロパティーが設定されている場合、OidcConfigurationMetadata issuer プロパティー値として使用されます。

不透明トークンを使用する場合は、次のコード例に示す方法でトークンをテストできます。

import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.quarkus.test.security.oidc.OidcSecurity;
import io.quarkus.test.security.oidc.TokenIntrospection;
import io.restassured.RestAssured;

@QuarkusTest
@TestHTTPEndpoint(ProtectedResource.class)
public class TestSecurityAuthTest {

    @Test
    @TestSecurity(user = "userOidc", roles = "viewer")
    @OidcSecurity(introspectionRequired = true,
        introspection = {
            @TokenIntrospection(key = "email", value = "user@gmail.com")
        }
    )
    public void testOidcWithClaimsUserInfoAndMetadata() {
        RestAssured.when().get("test-security-oidc-opaque-token").then()
                .body(is("userOidc:viewer:userOidc:viewer:user@gmail.com"));
    }

}

このコード例で使用されている ProtectedResource クラスは次のようになります。

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.quarkus.oidc.TokenIntrospection;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

@Path("/service")
@Authenticated
public class ProtectedResource {

    @Inject
    SecurityIdentity securityIdentity;
    @Inject
    TokenIntrospection introspection;

    @GET
    @Path("test-security-oidc-opaque-token")
    public String testSecurityOidcOpaqueToken() {
        return securityIdentity.getPrincipal().getName() + ":" + securityIdentity.getRoles().iterator().next()
            + ":" + introspection.getString("username")
            + ":" + introspection.getString("scope")
            + ":" + introspection.getString("email");
    }
}

@TestSecurityuser、および roles 属性は、TokenIntrospectionusername、および scope プロパティーとして使用できます。 io.quarkus.test.security.oidc.TokenIntrospection を使用して、email などの追加のイントロスペクションレスポンスプロパティーを追加します。

@TestSecurity@OidcSecurity は、メタアノテーションで組み合わせることができます。次の例を参照してください。

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    @TestSecurity(user = "userOidc", roles = "viewer")
    @OidcSecurity(introspectionRequired = true,
        introspection = {
            @TokenIntrospection(key = "email", value = "user@gmail.com")
        }
    )
    public @interface TestSecurityMetaAnnotation {

    }

これは、複数のテスト方法で同じセキュリティー設定セットを使用する必要がある場合に特に便利です。

ログでのエラーの確認

トークン検証エラーの詳細を確認するには、io.quarkus.oidc.runtime.OidcProvider および TRACE レベルのロギングを有効にします。

quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE

OidcProvider クライアント初期化エラーの詳細を確認するには、次のように io.quarkus.oidc.runtime.OidcRecorder および TRACE レベルのロギングを有効にします。

quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".min-level=TRACE

OIDC プロバイダーへの外部および内部アクセス

OIDC プロバイダーおよびその他のエンドポイントの外部からアクセス可能なトークンは、自動検出された URL や quarkus.oidc.auth-server-url 内部 URL を基準に設定された URL とは異なる HTTP(S) URL を持っている可能性があります。 たとえば、SPA が外部トークンエンドポイントアドレスからトークンを取得し、それをベアラートークンとして Quarkus に送信するとします。 その場合、エンドポイントから発行者の検証の失敗が報告される可能性があります。

このような場合に Keycloak を使用する場合は、KEYCLOAK_FRONTEND_URL システムプロパティーを外部からアクセス可能なベース URL に設定して Keycloak を起動してください。 他の OIDC プロバイダーを使用する場合は、プロバイダーのドキュメントを参照してください。

client-id プロパティーの使用

quarkus.oidc.client-id プロパティーは、現在のベアラートークンをリクエストした OIDC クライアントを特定します。 OIDC クライアントは、ブラウザーで実行される SPA アプリケーション、または Quarkus service アプリケーションにアクセストークンを伝播する Quarkus web-app コンフィデンシャルクライアントアプリケーションです。

このプロパティーは、service アプリケーションがトークンをリモートでイントロスペクトすることが予想される場合に必要です。これは、不透明トークンの場合は常に当てはまります。 このプロパティーは、ローカル JSON Web Token (JWT) 検証の場合にのみ省略可能です。

エンドポイントがリモートイントロスペクションエンドポイントへのアクセスを必要としない場合でも、quarkus.oidc.client-id プロパティーを設定することを推奨します。 これは、client-id が設定されている場合、それを使用してトークンオーディエンスを検証できるためです。 また、トークンの検証が失敗した場合にも client-id がログに含まれるため、特定のクライアントに発行されたトークンのトレーサビリティーが向上し、より長い期間にわたる分析が可能になります。

たとえば、OIDC プロバイダーでトークンオーディエンスを設定する場合は、次の設定パターンを検討してください。

# Set client-id
quarkus.oidc.client-id=quarkus-app
# Token audience claim must contain 'quarkus-app'
quarkus.oidc.token.audience=${quarkus.oidc.client-id}

quarkus.oidc.client-id を設定したが、いずれかの OIDC プロバイダーエンドポイントへのリモートアクセスがエンドポイントに必要ない場合 (イントロスペクション、トークンの取得など)、quarkus.oidc.credentials または同様のプロパティーを使用してクライアントシークレットを設定しないでください。その場合、クライアントシークレットは使用されないためです。

Quarkus web-app アプリケーションには、常に quarkus.oidc.client-id プロパティーが必要です。

HTTP リクエストが完了した後の認証

場合によっては、アクティブな HTTP リクエストコンテキストが存在しない場合に、特定のトークンの SecurityIdentity を作成する必要があります。 quarkus-oidc エクステンションは、トークンを SecurityIdentity インスタンスに変換するための io.quarkus.oidc.TenantIdentityProvider を提供します。 たとえば、HTTP リクエストが完了した後にトークンを検証する必要がある状況の 1 つとして、Vert.x イベントバス を使用してメッセージを処理する場合があります。 以下の例では、さまざまな CDI リクエストコンテキスト内で 'product-order' メッセージを使用しています。 したがって、注入された SecurityIdentity は、検証されたアイデンティティーを正しく表さず、匿名になります。

package org.acme.quickstart.oidc;

import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION;

import jakarta.inject.Inject;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import io.vertx.core.eventbus.EventBus;

@Path("order")
public class OrderResource {

    @Inject
    EventBus eventBus;

    @POST
    public void order(String product, @HeaderParam(AUTHORIZATION) String bearer) {
        String rawToken = bearer.substring("Bearer ".length()); (1)
        eventBus.publish("product-order", new Product(product, rawToken));
    }

    public static class Product {
         public String product;
         public String customerAccessToken;
         public Product() {
         }
         public Product(String product, String customerAccessToken) {
             this.product = product;
             this.customerAccessToken = customerAccessToken;
         }
    }
}
1 この時点では、プロアクティブ認証が無効な場合、トークンは検証されません。
package org.acme.quickstart.oidc;

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

import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.Tenant;
import io.quarkus.oidc.TenantIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.ConsumeEvent;
import io.smallrye.common.annotation.Blocking;

@ApplicationScoped
public class OrderService {

    @Tenant("tenantId")
    @Inject
    TenantIdentityProvider identityProvider;

    @Inject
    TenantIdentityProvider defaultIdentityProvider; (1)

    @Blocking
    @ConsumeEvent("product-order")
    void processOrder(OrderResource.Product product) {
        AccessTokenCredential tokenCredential = new AccessTokenCredential(product.customerAccessToken);
        SecurityIdentity securityIdentity = identityProvider.authenticate(tokenCredential).await().indefinitely(); (2)
        ...
    }

}
1 For the default tenant, the Tenant qualifier is optional.
2 トークンの検証を実行し、トークンを SecurityIdentity に変換します。

When the provider is used during an HTTP request, the tenant configuration can be resolved as described in the Using OpenID Connect Multi-Tenancy guide. However, when there is no active HTTP request, you must select the tenant explicitly with the io.quarkus.oidc.Tenant qualifier.

動的テナント設定解決 は現在サポートされていません。 動的テナントを必要とする認証は失敗します。

OIDC リクエストフィルター

You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more OidcRequestFilter implementations, which can update or add new request headers, and log requests. For more information, see OIDC request filters.

関連コンテンツ