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

OpenID Connect (OIDC)のマルチテナンシーの使用

このガイドでは、OpenID Connect (OIDC) アプリケーションがマルチテナントをサポートして、単一のアプリケーションから複数のテナントにサービスを提供する方法を説明します。 これらのテナントは、同じ OIDC プロバイダー内の異なるレルムまたはセキュリティードメイン、あるいは異なる OIDC プロバイダーである場合もあります。

SaaS 環境など、同じアプリケーションから複数の顧客にサービスを提供する場合、各顧客は個別のテナントとして機能します。 アプリケーションに対してマルチテナンシーサポートを有効にすると、Keycloak や Google などの異なる OIDC プロバイダーに対して認証する場合でも、テナントごとに異なる認証ポリシーをサポートできます。

Bearer Token Authorization を使用してテナントを認可するには、OpenID Connect (OIDC) ベアラートークン認証 ガイドを参照してください。

OIDC 認可コードフローを使用してテナントを認証および認可するには、Web アプリケーションを保護するための OpenID Connect 認可コードフローメカニズム ガイドを参照してください。

また、OpenID Connect (OIDC) 設定プロパティー のリファレンスガイドも参照してください。

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 17+がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.9.6

  • 動作するコンテナランタイム(Docker, Podman)

  • 使用したい場合は、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

  • jq tool

アーキテクチャ

この例では、次の 2 つのリソースメソッドをサポートする非常にシンプルなアプリケーションをビルドします。

  • /{tenant}

このリソースは、認証されたユーザーと現在のテナントについて、OIDC プロバイダーによって発行された ID トークンから取得した情報を返します。

  • /{tenant}/bearer

このリソースは、認証されたユーザーと現在のテナントについて、OIDC プロバイダーによって発行された Access Token から取得した情報を返します。

ソリューション

完全に理解するために、次の手順に従ってアプリケーションをビルドすることを推奨します。

あるいは、完成した例から始める場合は、Git リポジトリーをクローン (git clone -b 3.8 https://github.com/quarkusio/quarkus-quickstarts.git) するか、https://github.com/quarkusio/quarkus-quickstarts/archive/3.8.zip[アーカイブ] をダウンロードします。

ソリューションは、 security-openid-connect-multi-tenancy-quickstart ディレクトリーにあります。

Mavenプロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。

コマンドラインインタフェース
quarkus create app org.acme:security-openid-connect-multi-tenancy-quickstart \
    --extension='oidc,resteasy-reactive-jackson' \
    --no-code
cd security-openid-connect-multi-tenancy-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.8.6.1:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-openid-connect-multi-tenancy-quickstart \
    -Dextensions='oidc,resteasy-reactive-jackson' \
    -DnoCode
cd security-openid-connect-multi-tenancy-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

Windowsユーザーの場合:

  • cmdを使用する場合、(バックスラッシュ \ を使用せず、すべてを同じ行に書かないでください)。

  • Powershellを使用する場合は、 -D パラメータを二重引用符で囲んでください。例: "-DprojectArtifactId=security-openid-connect-multi-tenancy-quickstart"

Quarkus プロジェクトがすでに設定されている場合は、プロジェクトベースディレクトリーで次のコマンドを実行して、 oidc エクステンションをプロジェクトに追加します。

コマンドラインインタフェース
quarkus extension add oidc
Maven
./mvnw quarkus:add-extension -Dextensions='oidc'
Gradle
./gradlew addExtension --extensions='oidc'

これにより、 pom.xml ファイルに以下が追加されます:

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

アプリケーションの記述

まず、 /{tenant} エンドポイントを実装することから始めます。 以下のソースコードからわかるように、これは単なる通常の Jakarta REST リソースです。

package org.acme.quickstart.oidc;

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

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.quarkus.oidc.IdToken;

@Path("/{tenant}")
public class HomeResource {
    /**
     * Injection point for the ID Token issued by the OIDC provider.
     */
    @Inject
    @IdToken
    JsonWebToken idToken;

    /**
     * Injection point for the Access Token issued by the OIDC provider.
     */
    @Inject
    JsonWebToken accessToken;

    /**
     * Returns the ID Token info.
     * This endpoint exists only for demonstration purposes.
     * Do not expose this token in a real application.
     *
     * @return ID Token info
     */
    @GET
    @Produces("text/html")
    public String getIdTokenInfo() {
        StringBuilder response = new StringBuilder().append("<html>")
                .append("<body>");

        response.append("<h2>Welcome, ").append(this.idToken.getClaim("email").toString()).append("</h2>\n");
        response.append("<h3>You are accessing the application within tenant <b>").append(idToken.getIssuer()).append(" boundaries</b></h3>");

        return response.append("</body>").append("</html>").toString();
    }

    /**
     * Returns the Access Token info.
     * This endpoint exists only for demonstration purposes.
     * Do not expose this token in a real application.
     *
     * @return Access Token info
     */
    @GET
    @Produces("text/html")
    @Path("bearer")
    public String getAccessTokenInfo() {
        StringBuilder response = new StringBuilder().append("<html>")
                .append("<body>");

        response.append("<h2>Welcome, ").append(this.accessToken.getClaim("email").toString()).append("</h2>\n");
        response.append("<h3>You are accessing the application within tenant <b>").append(accessToken.getIssuer()).append(" boundaries</b></h3>");

        return response.append("</body>").append("</html>").toString();
    }
}

受信リクエストからテナントを解決し、 application.properties 内の特定の quarkus-oidc テナント設定にマップするには、テナント設定を動的に解決できる io.quarkus.oidc.TenantConfigResolver インターフェイスの実装を作成します。

package org.acme.quickstart.oidc;

import jakarta.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.config.ConfigProvider;

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

@ApplicationScoped
public class CustomTenantResolver implements TenantConfigResolver {

    @Override
    public Uni<OidcTenantConfig> resolve(RoutingContext context, OidcRequestContext<OidcTenantConfig> requestContext) {
        String path = context.request().path();

        if (path.startsWith("/tenant-a")) {
           String keycloakUrl = ConfigProvider.getConfig().getValue("keycloak.url", String.class);

            OidcTenantConfig config = new OidcTenantConfig();
            config.setTenantId("tenant-a");
            config.setAuthServerUrl(keycloakUrl + "/realms/tenant-a");
            config.setClientId("multi-tenant-client");
            config.getCredentials().setSecret("secret");
            config.setApplicationType(ApplicationType.HYBRID);
            return Uni.createFrom().item(config);
        } else {
            // resolve to default tenant config
            return Uni.createFrom().nullItem();
        }
    }
}

前述の実装では、テナントはリクエストパスから解決されます。 テナントを推測できない場合は、デフォルトのテナント設定を使用する必要があることを示すために null が返されます。

tenant-a アプリケーションタイプは hybrid です。提供されている場合、HTTP ベアラートークンを受け入れることができます。 それ以外の場合は、認証が必要なときに認可コードフローを開始します。

アプリケーションの設定

# Default tenant configuration
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=multi-tenant-client
quarkus.oidc.application-type=web-app

# Tenant A configuration is created dynamically in CustomTenantConfigResolver

# HTTP security configuration
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated

最初の設定は、リクエストからテナントを推測できない場合に使用するデフォルトのテナント設定です。 %prod` プロファイル接頭辞は、Dev Services For Keycloak を使用したマルチテナントアプリケーションのテストをサポートするために、 quarkus.oidc.auth-server-url とともに使用される点に注意してください。 この設定では、Keycloak インスタンスを使用してユーザーを認証します。

TenantConfigResolver によって提供される 2 番目の設定は、受信リクエストが tenant-a テナントにマップされるときに使用されます。

両方の設定は、異なる realms を使用しながら、同じ Keycloak サーバーインスタンスにマップされます。

または、 application.properties で直接テナント tenant-a を設定することもできます。

# Default tenant configuration
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=multi-tenant-client
quarkus.oidc.application-type=web-app

# Tenant A configuration
quarkus.oidc.tenant-a.auth-server-url=http://localhost:8180/realms/tenant-a
quarkus.oidc.tenant-a.client-id=multi-tenant-client
quarkus.oidc.tenant-a.application-type=web-app

# HTTP security configuration
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated

その場合は、カスタム TenantConfigResolver も使用して解決します。

package org.acme.quickstart.oidc;

import jakarta.enterprise.context.ApplicationScoped;

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

@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {

    @Override
    public String resolve(RoutingContext context) {
        String path = context.request().path();
        String[] parts = path.split("/");

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

        return parts[1];
    }
}

設定ファイルで複数のテナントを定義できます。 TenantResolver 実装からテナントを解決するときにそれらを正しくマップするには、それぞれに一意のエイリアスがあることを確認します。

ただし、 application.properties でテナントを設定し、 TenantResolver を使用してこれらを解決する静的テナント解決を使用することは、Dev Services for Keycloak を使用したエンドポイントのテストには機能しません。これは、リクエストが個々のテナントにどのようにマッピングされるかがわからず、テナント固有の quarkus.oidc.<tenant-id>.auth-server-url の値を動的に提供できないためです。したがって、 application.properties 内のテナント固有の URL に %prod 接頭辞を付けると、テストモードと開発モードの両方で機能しません。

現在のテナントが OIDC の web-app アプリケーションを表す場合、カスタムテナントリゾルバーがすべてのリクエストに対して呼び出されるまでに、現在の io.vertx.ext.web.RoutingContext には tenant-id 属性が含まれ、テナント固有の状態またはセッション Cookie のいずれかがすでに存在する場合に、コード認証フローとすでに認証されたリクエストを完了します。 したがって、複数の OIDC プロバイダーを使用する場合、 RoutingContexttenant-id 属性が設定されていない場合にのみ、パス固有のチェックを行ってテナント ID を解決する必要があります。以下はその例です。

package org.acme.quickstart.oidc;

import jakarta.enterprise.context.ApplicationScoped;

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

@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {

    @Override
    public String resolve(RoutingContext context) {
        String tenantId = context.get("tenant-id");
        if (tenantId != null) {
            return tenantId;
        } else {
            // Initial login request
            String path = context.request().path();
            String[] parts = path.split("/");

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

これは、カスタム TenantResolver が登録されていない場合に、Quarkus OIDC が静的カスタムテナントを解決する方法です。

同様の手法を TenantConfigResolver でも使用できます。コンテキストで提供される tenant-id は、以前のリクエストですでに準備されている OidcTenantConfig を返すことができます。

Hibernate ORM マルチテナンシー または MongoDB と Panache マルチテナンシー も使用していて、両方のテナント ID が同じで、 Vert.x RoutingContext から抽出する必要がある場合は、テナント ID を OIDC テナントリゾルバーから、Hibernate ORM テナントリゾルバーまたは MongoDB と Panache Mongo データベースリゾルバーへ RoutingContext 属性として渡すことができます。以下に例を示します。

public class CustomTenantResolver implements TenantResolver {

    @Override
    public String resolve(RoutingContext context) {
        String tenantId = extractTenantId(context);
        context.put("tenantId", tenantId);
        return tenantId;
    }
}

Keycloak サーバーの起動と設定

Keycloak サーバーを起動するには、Docker を使用して次のコマンドを実行します。

docker run --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:{keycloak.version} start-dev

where keycloak.version is set to 24.0.0 or higher.

localhost:8180 で Keycloak サーバーにアクセスします。

Keycloak 管理コンソールにアクセスするには、 admin ユーザーとしてログインします。 ユーザー名とパスワードは両方とも admin です。

次に、2 つのテナントのレルムをインポートします。

詳細は、 新規レルムの作成 方法に関する Keycloak ドキュメントを参照してください。

アプリケーションの実行と使用

開発者モードで実行

マイクロサービスをdevモードで実行する場合、次を実行して下さい:

コマンドラインインタフェース
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

JVM モードでの実行

開発モードでアプリケーションを試した後、標準の Java アプリケーションとして実行できます。

まず、コンパイルします:

コマンドラインインタフェース
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

次に、以下を実行してください:

java -jar target/quarkus-app/quarkus-run.jar

ネイティブモードでの実行

この同じデモはネイティブコードにコンパイルできます。変更は必要ありません。

これは、生成されたバイナリーにランタイムテクノロジーが含まれ、 最小限のリソースで実行するように最適化されているため、 実稼働環境に JVM をインストールする必要がなくなることを意味します。

コンパイルには少し時間がかかるため、この手順はデフォルトでオフになっています。 ネイティブビルドを有効にして再度ビルドしてみましょう。

コマンドラインインタフェース
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

しばらくすると、このバイナリーを直接実行できるようになります。

./target/security-openid-connect-multi-tenancy-quickstart-runner

アプリケーションのテスト

Dev Services for Keycloakの使用

Keycloak に対するインテグレーションテストには、Dev Services for Keycloak が推奨されます。 Dev Services for Keycloak は、テストコンテナーを起動して初期化します。設定されたレルムをインポートし、 CustomTenantResolver のベース Keycloak URL を設定して、レルム固有の URL を計算します。

まず、次の依存関係を追加します。

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

quarkus-test-keycloak-server は、レルム固有のアクセストークンを取得するためのユーティリティクラス io.quarkus.test.keycloak.client.KeycloakTestClient を提供します。これは、ベアラアクセストークンを想定する /{tenant}/bearer エンドポイントのテストに RestAssured と共に使用できます。 HtmlUnit は、 /{tenant} エンドポイントと認可コードフローをテストします。

次に、必要なレルムを設定します:

# Default tenant configuration
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=multi-tenant-client
quarkus.oidc.application-type=web-app

# Tenant A configuration is created dynamically in CustomTenantConfigResolver

# HTTP security configuration
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated

quarkus.keycloak.devservices.realm-path=default-tenant-realm.json,tenant-a-realm.json

最後に、JVM モードで実行されるテストを記述します。

package org.acme.quickstart.oidc;

import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;

import org.junit.jupiter.api.Test;

import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
import io.restassured.RestAssured;

@QuarkusTest
public class CodeFlowTest {

    KeycloakTestClient keycloakClient = new KeycloakTestClient();

    @Test
    public void testLogInDefaultTenant() throws IOException {
        try (final WebClient webClient = createWebClient()) {
            HtmlPage page = webClient.getPage("http://localhost:8081/default");

            assertEquals("Sign in to quarkus", page.getTitleText());

            HtmlForm loginForm = page.getForms().get(0);

            loginForm.getInputByName("username").setValueAttribute("alice");
            loginForm.getInputByName("password").setValueAttribute("alice");

            page = loginForm.getInputByName("login").click();

            assertTrue(page.asText().contains("tenant"));
        }
    }

    @Test
    public void testLogInTenantAWebApp() throws IOException {
        try (final WebClient webClient = createWebClient()) {
            HtmlPage page = webClient.getPage("http://localhost:8081/tenant-a");

            assertEquals("Sign in to tenant-a", page.getTitleText());

            HtmlForm loginForm = page.getForms().get(0);

            loginForm.getInputByName("username").setValueAttribute("alice");
            loginForm.getInputByName("password").setValueAttribute("alice");

            page = loginForm.getInputByName("login").click();

            assertTrue(page.asText().contains("alice@tenant-a.org"));
        }
    }

    @Test
    public void testLogInTenantABearerToken() throws IOException {
        RestAssured.given().auth().oauth2(getAccessToken()).when()
            .get("/tenant-a/bearer").then().body(containsString("alice@tenant-a.org"));
    }

    private String getAccessToken() {
        return keycloakClient.getRealmAccessToken("tenant-a", "alice", "alice", "multi-tenant-client", "secret");
    }

    private WebClient createWebClient() {
        WebClient webClient = new WebClient();
        webClient.setCssErrorHandler(new SilentCssErrorHandler());
        return webClient;
    }
}

ネイティブモードの場合:

package org.acme.quickstart.oidc;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class CodeFlowIT extends CodeFlowTest {
}

初期化および設定方法の詳細は、Dev Services for Keycloak を参照してください。

ブラウザーの使用

アプリケーションの動作確認は、ブラウザを起動して以下のURLにアクセスしてください:

すべてが期待どおりに動作する場合、認証のために Keycloak サーバーにリダイレクトされます。 要求されたパスは、設定ファイルでマッピングされていない default テナントを定義していることに注意してください。 この場合、デフォルトの設定が使用されます。

アプリケーションを認証するには、Keycloak ログインページで次の認証情報を入力します。

  • ユーザー名: alice

  • パスわ=ド: alice

Login ボタンをクリックすると、アプリケーションにリダイレクトされます。

次の URL でアプリケーションにアクセスを試みます。

Keycloak ログインページに再度リダイレクトされます。 ただし、今回は別のレルムを使用して認証します。

どちらの場合も、ユーザーが正常に認証されると、ランディングページにユーザーの名前とメールアドレスが表示されます。 alice は両方のテナントに存在しますが、アプリケーションはそれらを別々のレルム内の別個のユーザーとして扱います。

静的テナント設定の解決

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

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

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

You can return the tenant id of either a or b from quarkus.oidc.TenantResolver:

import quarkus.oidc.TenantResolver;

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

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

Default resolution

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

次の 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=google
quarkus.oidc.github.client-id=${github-client-id}
quarkus.oidc.github.credentials.secret=${github-client-secret}
quarkus.oidc.github.authentication.redirect-path=/signed-in

提供された例では、両方のテナントが 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

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

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

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

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

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

アプリケーションが 2 つの OIDC テナント (hr テナントとデフォルトテナント) をサポートしていると仮定すると、 @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!";
    }
}
1 io.quarkus.oidc.Tenant アノテーションは、リソースクラスまたはリソースメソッドのいずれかに配置する必要があります。

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

サポートしたいさまざまなテナントに対して、より動的な設定が必要で、設定ファイルに複数のエントリーを入れたくない場合は、 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.createFromItem(createTenantConfig());
        }

        //Resolve to default tenant configuration
        return null;
    }

    private Supplier<OidcTenantConfig> createTenantConfig() {
        final OidcTenantConfig config = new OidcTenantConfig();

        config.setTenantId("tenant-c");
        config.setAuthServerUrl("http://localhost:8180/realms/tenant-c");
        config.setClientId("multi-tenant-client");
        OidcTenantConfig.Credentials credentials = new OidcTenantConfig.Credentials();

        credentials.setSecret("my-secret");

        config.setCredentials(credentials);

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

        return () -> config;
    }
}

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

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

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

The simplest option for resolving the OIDC web-app application configuration is to follow the steps described in the Default resolution section.

Try one of the options below if the default resolution strategy does not work for your application setup.

Several options are available for selecting the tenant configuration that should be used to secure the current HTTP request for both service and web-app OIDC applications, such as:

  • Check the URL paths. For example, a tenant-service configuration must be used for the /service paths, while a tenant-manage configuration must be used for the /management paths.

  • Check the HTTP headers. For example, with a URL path always being /service, a header such as Realm: service or Realm: management can help to select between the tenant-service and tenant-manage configurations.

  • Check the URL query parameters. It can work similarly to the way the headers are used to select the tenant configuration.

これらのオプションはすべて、OIDC service アプリケーションのカスタムの TenantResolver 実装や TenantConfigResolver 実装を使用して簡単に実装できます。

However, due to an HTTP redirect required to complete the code authentication flow for the OIDC web-app applications, a custom HTTP cookie might be needed to select the same tenant configuration before and after this redirect request because:

  • The URL path might not be the same after the redirect request if a single redirect URL has been registered in the OIDC provider. The original request path can be restored after the tenant configuration has been resolved.

  • The HTTP headers used during the original request are unavailable after the redirect.

  • The custom URL query parameters are restored after the redirect but only after the tenant configuration is resolved.

リダイレクトの前後に web-app アプリケーションのテナント設定を解決するための情報を確実に利用できるようにするための 1 つのオプションは、Cookie を使用することです。以下はその例です。

package org.acme.quickstart.oidc;

import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.oidc.TenantResolver;
import io.vertx.core.http.Cookie;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {

    @Override
    public String resolve(RoutingContext context) {
        List<String> tenantIdQuery = context.queryParam("tenantId");
        if (!tenantIdQuery.isEmpty()) {
            String tenantId = tenantIdQuery.get(0);
            context.response().addCookie(Cookie.cookie("tenant", tenantId));
            return tenantId;
        } else if (!context.request().cookies("tenant").isEmpty()) {
            return context.request().getCookie("tenant").getValue();
        }

        return null;
    }
}

テナント設定を無効にする

現在のリクエストからテナントを推測できず、デフォルトのテナント設定へのフォールバックが必要な場合、カスタム TenantResolver および TenantConfigResolver 実装は、 null を返すことがあります。

カスタムリゾルバーが常にテナントを解決することを期待する場合は、デフォルトのテナント解決を設定する必要はありません。

  • デフォルトのテナント設定をオフにするには、 quarkus.oidc.tenant-enabled=false を設定します。

quarkus.oidc.auth-server-url が設定されていない場合、デフォルトのテナント設定は自動的に無効になりますが、カスタムテナント設定が使用可能であるか、 TenantConfigResolver が登録されています。

テナント固有の設定も無効にできる点に注意してください (例: quarkus.oidc.tenant-a.tenant-enabled=false)。

関連コンテンツ