5.10. テナント解決


5.10.1. テナント解決命令

OIDC テナントは次の順序で解決されます。

  1. プロアクティブ認証が無効な場合は、最初に io.quarkus.oidc.Tenant アノテーションがチェックされます。
  2. カスタムの TenantConfigResolver を使用した動的なテナント解決。
  3. カスタムの TenantResolver、設定済みのテナントパス、テナント ID として最後のリクエストパスセグメントをデフォルトに設定する方法のうち、いずれかを使用した静的テナント解決。

上記手順の後にテナント ID が解決されていない場合は、最後にデフォルトの OIDC テナントが選択されます。

詳細は、以下のセクションを参照してください。

さらに、OIDC web-app アプリケーションの場合、状態 Cookie とセッション Cookie が、認可コードフローの開始時に上記のいずれかの方法を使用して解決されたテナントに関するヒントを提供します。詳細は、OIDC web-app アプリケーションのテナント解決 セクションを参照してください。

5.10.2. アノテーションで解決する

io.quarkus.oidc.TenantResolver を使用する代わりに、io.quarkus.oidc.Tenant アノテーションを使用してテナント識別子を解決することができます。

注記

これを機能させるには、プロアクティブ HTTP 認証を無効にする必要があります (quarkus.http.auth.proactive=false)。詳細は、プロアクティブ認証 ガイドを参照してください。

アプリケーションが hr テナントと default テナントの 2 つの OIDC テナントをサポートしていると仮定すると、@Tenant("hr") を持つすべてのリソースメソッドとクラスは、quarkus.oidc.hr.auth-server-url によって設定された OIDC プロバイダーを使用して認証されます。対照的に、他のすべてのクラスとメソッドは、引き続きデフォルトの OIDC プロバイダーを使用して認証されます。

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.oidc.Tenant;
import io.quarkus.security.Authenticated;

@Authenticated
@Path("/api/hello")
public class HelloResource {

    @Tenant("hr") 
1

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String sayHello() {
        return "Hello!";
    }
}
Copy to Clipboard Toggle word wrap
1
io.quarkus.oidc.Tenant アノテーションは、リソースクラスまたはリソースメソッドのいずれかに配置する必要があります。
ヒント

上記の例では、sayHello エンドポイントの認証が @Authenticated アノテーションによって適用されます。

または、HTTP セキュリティーポリシー を使用してエンドポイントを保護する場合に @Tenant アノテーションを有効にするには、次の例に示すように、このポリシーの権限チェックを遅延する必要があります。

quarkus.http.auth.permission.authenticated.paths=/api/hello
quarkus.http.auth.permission.authenticated.methods=GET
quarkus.http.auth.permission.authenticated.policy=authenticated
quarkus.http.auth.permission.authenticated.applies-to=JAXRS 
1
Copy to Clipboard Toggle word wrap
1
@Tenant アノテーションを使用してテナントを選択した後、Quarkus に HTTP 権限チェックを実行するように指示します。

5.10.3. 動的なテナント設定の解決

サポートするさまざまなテナントに対してより動的な設定が必要であり、設定ファイルに複数のエントリーを含めたくない場合は、io.quarkus.oidc.TenantConfigResolver を使用できます。

このインターフェイスを使用すると、実行時にテナント設定を動的に作成できます。

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import java.util.function.Supplier;

import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TenantConfigResolver;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
        String path = context.request().path();
        String[] parts = path.split("/");

        if (parts.length == 0) {
            //Resolve to default tenant configuration
            return null;
        }

        if ("tenant-c".equals(parts[1])) {
            // Do 'return requestContext.runBlocking(createTenantConfig());'
            // if a blocking call is required to create a tenant config,
            return Uni.createFrom().item(createTenantConfig());
        }

        //Resolve to default tenant configuration
        return null;
    }

    private Supplier<OidcTenantConfig> createTenantConfig() {
        final OidcTenantConfig config = OidcTenantConfig
                .authServerUrl("http://localhost:8180/realms/tenant-c")
                .tenantId("tenant-c")
                .clientId("multi-tenant-client")
                .credentials("my-secret")
                .build();

        // Any other setting supported by the quarkus-oidc extension

        return () -> config;
    }
}
Copy to Clipboard Toggle word wrap

このメソッドによって返される OidcTenantConfig は、application.properties. から oidc namespace 設定を解析するために使用されるものと同じです。quarkus-oidc エクステンションでサポートされている任意の設定を使用して、これを入力できます。

動的テナントリゾルバーが null を返す場合は、次に 静的テナント設定の解決 が試行されます。

5.10.4. 静的テナント設定の解決

application.properties ファイルで複数のテナント設定を設定する場合は、テナント識別子の解決方法を指定するだけで済みます。テナント識別子の解決を設定するには、次のいずれかのオプションを使用します。

これらのテナント解決オプションは、テナント ID が解決されるまで、リストされている順序で試行されます。テナント ID が未解決のまま (null) の場合は、デフォルト (名前なし) のテナント設定が選択されます。

5.10.4.1. TenantResolver で解決する

次の application.properties の例は、TenantResolver メソッドを使用して、a および b という名前の 2 つのテナントのテナント ID を解決する方法を示しています。

# Tenant 'a' configuration
quarkus.oidc.a.auth-server-url=http://localhost:8180/realms/quarkus-a
quarkus.oidc.a.client-id=client-a
quarkus.oidc.a.credentials.secret=client-a-secret

# Tenant 'b' configuration
quarkus.oidc.b.auth-server-url=http://localhost:8180/realms/quarkus-b
quarkus.oidc.b.client-id=client-b
quarkus.oidc.b.credentials.secret=client-b-secret
Copy to Clipboard Toggle word wrap

io.quarkus.oidc.TenantResolver から a または b のテナント ID を返すことができます。

import io.quarkus.oidc.TenantResolver;
import io.vertx.ext.web.RoutingContext;

public class CustomTenantResolver implements TenantResolver {

    @Override
    public String resolve(RoutingContext context) {
        String path = context.request().path();
        if (path.endsWith("a")) {
            return "a";
        } else if (path.endsWith("b")) {
            return "b";
        } else {
            // default tenant
            return null;
        }
    }
}
Copy to Clipboard Toggle word wrap

この例では、最後のリクエストパスセグメントの値はテナント ID ですが、必要に応じて、より複雑なテナント ID 解決ロジックを実装できます。

5.10.4.2. テナントパスを設定する

io.quarkus.oidc.TenantResolver を使用する代わりに、quarkus.oidc.tenant-paths 設定プロパティーを使用してテナント識別子を解決することができます。前の例で使用した HelloResource リソースの sayHello エンドポイントに hr テナントを選択する方法は次のとおりです。

quarkus.oidc.hr.tenant-paths=/api/hello 
1

quarkus.oidc.a.tenant-paths=/api/* 
2

quarkus.oidc.b.tenant-paths=/*/hello 
3
Copy to Clipboard Toggle word wrap
1
前の例の quarkus.http.auth.permission.authenticated.paths=/api/hello 設定プロパティーと同じパスマッチングルールが適用されます。
2
パスの末尾に配置されたワイルドカードは、任意の数のパスセグメントを表します。しかし、このパスは /api/hello よりも具体性が低いため、sayHello エンドポイントを保護するために hr テナントが使用されます。
3
/*/hello 内のワイルドカードは、正確に 1 つのパスセグメントを表します。しかし、ワイルドカードは api よりも具体性が低いため、hr テナントが使用されます。
ヒント

パスマッチングメカニズムは、設定を使用した認可 の場合とまったく同じように機能します。

5.10.4.3. 最後のリクエストパスセグメントをテナント ID として使用する

テナント識別子のデフォルトの解決は規則に基づいており、認証要求には要求パスの最後のセグメントにテナント識別子が含まれている必要があります。

次の application.properties の例は、googlegithub という名前の 2 つのテナントを設定する方法を示しています。

# Tenant 'google' configuration
quarkus.oidc.google.provider=google
quarkus.oidc.google.client-id=${google-client-id}
quarkus.oidc.google.credentials.secret=${google-client-secret}
quarkus.oidc.google.authentication.redirect-path=/signed-in

# Tenant 'github' configuration
quarkus.oidc.github.provider=github
quarkus.oidc.github.client-id=${github-client-id}
quarkus.oidc.github.credentials.secret=${github-client-secret}
quarkus.oidc.github.authentication.redirect-path=/signed-in
Copy to Clipboard Toggle word wrap

上記の例では、両方のテナントが OIDC web-app アプリケーションを設定して、認可コードフローを使用してユーザーを認証し、認証後にセッション Cookie を生成することを要求します。Google または GitHub が現在のユーザーを認証すると、ユーザーは、JAX-RS エンドポイント上のセキュリティーで保護されたリソースパスなど、認証されたユーザーの /signed-in 領域に戻されます。

最後に、デフォルトのテナント解決を完了するには、次の設定プロパティーを設定します。

quarkus.http.auth.permission.login.paths=/google,/github
quarkus.http.auth.permission.login.policy=authenticated
Copy to Clipboard Toggle word wrap

エンドポイントが http://localhost:8080 で実行している場合は、特定の /google または /github JAX-RS リソースパスを追加せずに、ユーザーが http://localhost:8080/google または http://localhost:8080/github にログインするための UI オプションを提供することもできます。認証が完了すると、テナント識別子もセッション Cookie 名に記録されます。したがって、認証されたユーザーは、セキュリティー保護された URL に google または github のパス値を含める必要なく、セキュリティー保護されたアプリケーション領域にアクセスできます。

デフォルトの解決は、ベアラートークン認証でも機能します。ただし、テナント識別子は常に最後のパスセグメント値として設定する必要があるため、あまり実用的ではない可能性があります。

5.10.4.4. トークン発行者のクレームでテナントを解決する

ベアラートークン認証をサポートする OIDC テナントは、アクセストークンの発行者を使用して解決できます。発行者ベースの解決を機能させるには、次の条件を満たす必要があります。

  • アクセストークンが JWT 形式であり、発行者 (iss) トークンクレームを含んでいる必要があります。
  • アプリケーションタイプが service または hybrid である OIDC テナントのみが考慮されます。これらのテナントでは、トークン発行者が検出または設定されている必要があります。

発行者ベースの解決は、quarkus.oidc.resolve-tenants-with-issuer プロパティーで有効になります。以下に例を示します。

quarkus.oidc.resolve-tenants-with-issuer=true 
1


quarkus.oidc.tenant-a.auth-server-url=${tenant-a-oidc-provider} 
2

quarkus.oidc.tenant-a.client-id=${tenant-a-client-id}
quarkus.oidc.tenant-a.credentials.secret=${tenant-a-client-secret}

quarkus.oidc.tenant-b.auth-server-url=${tenant-b-oidc-provider} 
3

quarkus.oidc.tenant-b.discover-enabled=false
quarkus.oidc.tenant-b.token.issuer=${tenant-b-oidc-provider}/issuer
quarkus.oidc.tenant-b.jwks-path=/jwks
quarkus.oidc.tenant-b.token-path=/tokens
quarkus.oidc.tenant-b.client-id=${tenant-b-client-id}
quarkus.oidc.tenant-b.credentials.secret=${tenant-b-client-secret}
Copy to Clipboard Toggle word wrap
1
テナント tenant-atenant-b は、JWT アクセストークンの発行者 iss クレーム値を使用して解決されます。
2
テナント tenant-a は、OIDC プロバイダーの既知の設定エンドポイントから issuer を検出します。
3
テナント tenant-b は、OIDC プロバイダーが検出をサポートしていないため、issuer を設定します。

5.10.5. OIDC web-app アプリケーションのテナント解決

OIDC web-app アプリケーションのテナント解決は、OIDC テナント固有の設定が次の各ステップの実行方法に影響する場合、認可コードフロー中に少なくとも 3 回実行する必要があります。

ステップ 1: 認証されていないユーザーがエンドポイントにアクセスし、OIDC プロバイダーにリダイレクトされる

認証されていないユーザーが保護されたパスにアクセスすると、ユーザーは認証のために OIDC プロバイダーにリダイレクトされ、テナント設定を使用してリダイレクト URI が構築されます。

静的テナント設定の解決 および 動的テナント設定の解決 セクションにリストされているすべての静的および動的テナント解決オプションを使用して、テナントを解決できます。

ステップ 2: ユーザーがエンドポイントにリダイレクトされる

プロバイダー認証後、ユーザーは Quarkus エンドポイントにリダイレクトされ、認可コードフローを完了するためにテナント設定が使用されます。

静的テナント設定の解決 および 動的テナント設定の解決 セクションにリストされているすべての静的および動的テナント解決オプションを使用して、テナントを解決できます。テナント解決が開始する前に、認可コードフローの state cookie を使用して、すでに解決されたテナント設定 ID が RoutingContext の tenant-id 属性として設定されます。これは、カスタムの動的 TenantConfigResolver テナントリゾルバーと静的 TenantResolver テナントリゾルバーの両方がこれをチェックできます。

ステップ 3: 認証されたユーザーがセッション Cookie を使用して保護されたパスにアクセスする

テナント設定によって、セッション Cookie の検証および更新方法が決定されます。テナント解決が開始する前に、認可コードフローの session cookie を使用して、すでに解決されたテナント設定 ID が RoutingContext の tenant-id 属性として設定されます。カスタムの動的 TenantConfigResolver テナントリゾルバーと静的 TenantResolver テナントリゾルバーの両方がこれをチェックできます。

たとえば、カスタムの TenantConfigResolver が、すでに解決されているテナント設定の作成を回避する方法は次のとおりです。これを回避しないと、データベースやその他のリモートソースからの読み取りをブロックしなければならない可能性があります。

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
import io.quarkus.oidc.TenantConfigResolver;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
        String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
        if (resolvedTenantId != null) { 
1

            return null;
        }

        String path = context.request().path(); 
2

        if (path.endsWith("tenant-a")) {
            return Uni.createFrom().item(createTenantConfig("tenant-a", "client-a", "secret-a"));
        } else if (path.endsWith("tenant-b")) {
            return Uni.createFrom().item(createTenantConfig("tenant-b", "client-b", "secret-b"));
        }

        // Default tenant id
        return null;
    }

    private OidcTenantConfig createTenantConfig(String tenantId, String clientId, String secret) {
        final OidcTenantConfig config = OidcTenantConfig
                .authServerUrl("http://localhost:8180/realms/"  + tenantId)
                .tenantId(tenantId)
                .clientId(clientId)
                .credentials(secret)
                .applicationType(ApplicationType.WEB_APP)
                .build();
        return config;
    }
}
Copy to Clipboard Toggle word wrap
1
テナント設定が以前に解決されている場合は、すでに解決されたテナント設定を Quarkus で使用します。
2
テナント設定を作成するためのリクエストパスを確認します。

デフォルトの設定は次のようになります。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/default
quarkus.oidc.client-id=client-default
quarkus.oidc.credentials.secret=secret-default
quarkus.oidc.application-type=web-app
Copy to Clipboard Toggle word wrap

上記の例では、tenant-atenant-b、およびデフォルトテナントがすべて同じエンドポイントパスを保護するために使用されていることを前提としています。つまり、ユーザーが tenant-a 設定で認証されると、このユーザーは、ログアウトしてセッション Cookie がクリアされるか期限切れになるまで、tenant-b またはデフォルト設定で認証することを選択できなくなります。

複数の OIDC web-app テナントがテナント固有のパスを保護する状況はあまり一般的ではなく、特別な注意が必要です。tenant-atenant-b、デフォルトテナントなどの複数の OIDC web-app テナントを使用してテナント固有のパスへのアクセスを制御する場合、1 つの OIDC プロバイダーで認証されたユーザーは、別のプロバイダーによる認証を必要とするパスにアクセスできないようにする必要があります。そうしないと、結果が予測不可能になり、予期しない認証エラーが発生する可能性が高くなります。たとえば、tenant-a 認証に Keycloak 認証が必要で、tenant-b 認証に Auth0 認証が必要な場合、tenant-a の認証済みユーザーが tenant-b 設定で保護されたパスにアクセスしようとすると、セッション Cookie が検証されません。これは、Auth0 公開検証キーを使用しても、Keycloak によって署名されたトークンを検証できないためです。複数の web-app テナントが互いに競合するのを回避するには、次の例に示すように、テナント固有のセッションパスを設定するのが簡単であり、推奨されます。

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
import io.quarkus.oidc.TenantConfigResolver;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
        String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
        if (resolvedTenantId != null) { 
1

            return null;
        }

        String path = context.request().path(); 
2

        if (path.endsWith("tenant-a")) {
            return Uni.createFrom().item(createTenantConfig("tenant-a", "/tenant-a", "client-a", "secret-a"));
        } else if (path.endsWith("tenant-b")) {
            return Uni.createFrom().item(createTenantConfig("tenant-b", "/tenant-b", "client-b", "secret-b"));
        }

        // Default tenant id
        return null;
    }

    private OidcTenantConfig createTenantConfig(String tenantId, String cookiePath, String clientId, String secret) {
        final OidcTenantConfig config = OidcTenantConfig
                .authServerUrl("http://localhost:8180/realms/" + tenantId)
                .tenantId(tenantId)
                .clientId(clientId)
                .credentials(secret)
                .applicationType(ApplicationType.WEB_APP)
                .authentication().cookiePath(cookiePath).end()  
3

                .build();
        return config;
    }
}
Copy to Clipboard Toggle word wrap
1
テナント設定が以前に解決されている場合は、すでに解決されたテナント設定を Quarkus で使用します。
2
テナント設定を作成するためのリクエストパスを確認します。
3
テナント固有の Cookie パスを設定し、セッション Cookie がそれを作成したテナントにのみ認識されるようにします。

デフォルトのテナント設定を次のように調整する必要があります。

quarkus.oidc.auth-server-url=http://localhost:8180/realms/default
quarkus.oidc.client-id=client-default
quarkus.oidc.credentials.secret=secret-default
quarkus.oidc.authentication.cookie-path=/default
quarkus.oidc.application-type=web-app
Copy to Clipboard Toggle word wrap

複数の OIDC web-app テナントがテナント固有のパスを保護するときに、同じセッション Cookie パスを使用することは推奨されません。これは、カスタムリゾルバーからの管理がさらに必要になるため、回避する必要があります。次に例を示します。

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.OidcRequestContext;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.ApplicationType;
import io.quarkus.oidc.TenantConfigResolver;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantConfigResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {

        String path = context.request().path(); 
1

        if (path.endsWith("tenant-a")) {
            String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
	    if (resolvedTenantId != null) {
	        if ("tenant-a".equals(resolvedTenantId)) { 
2

	            return null;
	        } else {
	           // Require a "tenant-a" authentication
                   context.remove(OidcUtils.TENANT_ID_ATTRIBUTE); 
3

	        }
            }
            return Uni.createFrom().item(createTenantConfig("tenant-a", "client-a", "secret-a"));
        } else if (path.endsWith("tenant-b")) {
            String resolvedTenantId = context.get(OidcUtils.TENANT_ID_ATTRIBUTE);
	    if (resolvedTenantId != null) {
	        if ("tenant-b".equals(resolvedTenantId)) { 
4

	            return null;
	        } else {
	            // Require a "tenant-b" authentication
                   context.remove(OidcUtils.TENANT_ID_ATTRIBUTE); 
5

	        }
            }
            return Uni.createFrom().item(createTenantConfig("tenant-b", "client-b", "secret-b"));
        }

        // Set default tenant id
        context.put(OidcUtils.TENANT_ID_ATTRIBUTE, OidcUtils.DEFAULT_TENANT_ID); 
6

        return null;
    }

    private OidcTenantConfig createTenantConfig(String tenantId, String clientId, String secret) {
        final OidcTenantConfig config = OidcTenantConfig
            .authServerUrl("http://localhost:8180/realms/"  + tenantId)
            .tenantId(tenantId)
            .clientId(clientId)
            .credentials(secret)
            .applicationType(ApplicationType.WEB_APP)
            .build();
        return config;
    }
}
Copy to Clipboard Toggle word wrap
1
テナント設定を作成するためのリクエストパスを確認します。
2 4
現在のパスに対してテナント設定がすでに解決されていると予想される場合に、すでに解決されたテナント設定を Quarkus で使用します。
3 5
現在のパスに対してテナント設定がすでに解決されていると予想されない場合に、tenant-id 属性を削除します。
6
他のすべてのパスにデフォルトのテナントを使用します。これは、tenant-id 属性を削除することと同じです。
トップに戻る
Red Hat logoGithubredditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

Red Hat ドキュメントについて

Red Hat をお使いのお客様が、信頼できるコンテンツが含まれている製品やサービスを活用することで、イノベーションを行い、目標を達成できるようにします。 最新の更新を見る.

多様性を受け入れるオープンソースの強化

Red Hat では、コード、ドキュメント、Web プロパティーにおける配慮に欠ける用語の置き換えに取り組んでいます。このような変更は、段階的に実施される予定です。詳細情報: Red Hat ブログ.

会社概要

Red Hat は、企業がコアとなるデータセンターからネットワークエッジに至るまで、各種プラットフォームや環境全体で作業を簡素化できるように、強化されたソリューションを提供しています。

Theme

© 2025 Red Hat