OpenID Connect (OIDC) クライアントとトークンの伝播


Red Hat build of Quarkus 3.8

Red Hat Customer Content Services

概要

このガイドでは、トークン管理、リクエストのフィルタリング、アプリケーションのセットアップなど、OpenID Connect (OIDC) クライアントとトークンの伝播を設定および使用する方法を詳しく説明します。

Red Hat build of Quarkus ドキュメントへのフィードバックの提供

エラーを報告したり、ドキュメントを改善したりするには、Red Hat Jira アカウントにログインし、課題を送信してください。Red Hat Jira アカウントをお持ちでない場合は、アカウントを作成するように求められます。

手順

  1. 次のリンクをクリックして チケットを作成します
  2. Summary に課題の簡単な説明を入力します。
  3. Description に課題や機能拡張の詳細な説明を入力します。問題があるドキュメントのセクションへの URL も記載してください。
  4. Submit をクリックすると、課題が作成され、適切なドキュメントチームに転送されます。

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

Red Hat では、コード、ドキュメント、Web プロパティーにおける配慮に欠ける用語の置き換えに取り組んでいます。まずは、マスター (master)、スレーブ (slave)、ブラックリスト (blacklist)、ホワイトリスト (whitelist) の 4 つの用語の置き換えから始めます。この取り組みは膨大な作業を要するため、用語の置き換えは、今後の複数のリリースにわたって段階的に実施されます。詳細は、Red Hat CTO である Chris Wright のメッセージ をご覧ください。

第1章 OpenID Connect (OIDC) と OAuth2 クライアントおよびフィルター

Quarkus エクステンションは、トークンの取得、更新、伝播に重点を置いて、OpenID Connect および OAuth 2.0 アクセストークンの管理に使用できます。

これには、以下のパラメーターが含まれます。

  • quarkus-oidc-clientquarkus-oidc-client-reactive-filter、および quarkus-oidc-client-filter エクステンションを使用して、OpenID Connect および Keycloak などの OAuth 2.0 準拠の認可サーバーからアクセストークンを取得および更新します。
  • quarkus-oidc-token-propagation-reactive および quarkus-oidc-token-propagation エクステンションを使用して、現在の Bearer または Authorization Code Flow アクセストークンを伝播します。

これらのエクステンションによって管理されるアクセストークンは、リモートサービスにアクセスするための HTTP 認証ベアラートークンとして使用できます。

OpenID Connect クライアントとトークンの伝播クイックスタート も参照してください。

1.1. OidcClient

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

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-client</artifactId>
</dependency>

quarkus-oidc-client エクステンションは、SmallRye Mutiny Uni および Vert.x WebClient を使用してトークンを取得および更新するのに使用できるリアクティブ io.quarkus.oidc.client.OidcClient を提供します。

OidcClient はビルド時に IDP トークンエンドポイント URL を使用して初期化されます。これは自動検出または手動で設定できます。OidcClient はこのエンドポイントを使用して、client_credentialspassword などのトークングラントを使用してアクセストークンを取得し、refresh_token グラントを使用してトークンを更新します。

1.1.1. トークンエンドポイントの設定

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

たとえば、次の Keycloak URL があるとします。

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

OidcClient は、トークンエンドポイント URL が http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens であることを検出します。

また、検出エンドポイントが使用できない場合、または検出エンドポイントのラウンドトリップを節約したい場合は、検出を無効にして、相対パス値を使用してトークンエンドポイントアドレスを設定できます。以下に例を示します。

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

検出なしでトークンエンドポイント URL を設定するよりコンパクトな方法は、quarkus.oidc-client.token-path を絶対 URL に設定することです。

quarkus.oidc-client.token-path=http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens

この場合、quarkus.oidc-client.auth-server-urlquarkus.oidc-client.discovery-enabled を設定する必要はありません。

1.1.2. サポートされているトークングラント

OidcClient がトークンを取得するために使用できる主なトークングラントは、client_credentials グラント (デフォルト) と password グラントです。

1.1.2.1. クライアントクレデンシャルのグラント

OidcClientclient_credentials グラントを使用するように設定する方法は次のとおりです。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret

client_credentials グラントにより、quarkus.oidc-client.grant-options.client.<param-name>=<value> を使用してトークンリクエストの追加パラメーターを設定できます。audience パラメーターを使用して、対象のトークンの受信者を設定する方法は次のとおりです。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
# 'client' is a shortcut for `client_credentials`
quarkus.oidc-client.grant.type=client
quarkus.oidc-client.grant-options.client.audience=https://example.com/api
1.1.2.2. パスワードグラント

password グラントを使用するように OidcClient を設定する方法は次のとおりです。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

クライアントクレデンシャルのグラントをカスタマイズする方法と同様に、quarkus.oidc-client.grant-options.password 設定接頭辞を使用してさらにカスタマイズできます。

1.1.2.3. その他のグラント

OidcClient は、設定でキャプチャーできない追加の入力パラメーターを必要とする許可を使用してトークンを取得するのにも役立ちます。これらの許可は、refresh_token (外部更新トークンを使用)、authorization_code、および現在のアクセストークンを交換するために使用できる 2 つの許可、つまり urn:ietf:params:oauth:grant-type:token-exchange および urn:ietf:params:oauth:grant-type:jwt-bearer です。

アクセストークンを取得する必要があり、既存の更新トークンを現在の Quarkus エンドポイントに送信している場合は、refresh_token グラントを使用する必要があります。この許可では、帯域外更新トークンを使用して新しいトークンセットを取得します。この場合は、OidcClient を次のように設定します。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=refresh

次に、提供された更新トークンを使用して OidcClient.refreshTokens メソッドを使用してアクセストークンを取得できます。

複雑なマイクロサービスアプリケーションを構築していて、同じ Bearer トークンが複数のサービスに伝播されて使用されるのを避ける必要がある場合、urn:ietf:params:oauth:grant-type:token-exchange または urn:ietf:params:oauth:grant-type:jwt-bearer グラントの使用が必要になることがあります。詳細は、MicroProfile RestClient Reactive フィルターでのトークンの伝播 および MicroProfile RestClient フィルターでのトークンの伝播 を参照してください。

何らかの理由で、Quarkus OIDC エクステンション を使用して認可コードフローをサポートできない場合は、OidcClient を使用した authorization code グラントのサポートが必要になる場合があります。認可コードフローを実装する十分な理由がある場合は、次のように OidcClient を設定できます。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=code

次に、OidcClient.accessTokens メソッドを使用して、追加プロパティーのマップを受け入れ、現在の coderedirect_uri パラメーターを渡して、トークンの認可コードを交換できます。

OidcClient は、urn:openid:params:grant-type:ciba グラントもサポートします。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=ciba

次に、OidcClient.accessTokens メソッドを使用して追加プロパティーのマップを受け入れ、auth_req_id パラメーターを渡してトークン認可コードを交換できます。

1.1.2.4. スコープの付与

発行されたアクセストークンに特定のスコープのセットを関連付けるように要求しないといけない場合があります。専用の quarkus.oidc-client.scopes リストプロパティーを使用します (例: quarkus.oidc-client.scopes=email,phone)。

1.1.3. OidcClient の直接使用

次のように OidcClient を直接使用できます。

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

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.Tokens;

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

    @Inject
    OidcClient client;

    volatile Tokens currentTokens;

    @PostConstruct
    public void init() {
        currentTokens = client.getTokens().await().indefinitely();
    }

    @GET
    public String getResponse() {

        Tokens tokens = currentTokens;
        if (tokens.isAccessTokenExpired()) {
            // Add @Blocking method annotation if this code is used with Reactive RestClient
            tokens = client.refreshTokens(tokens.getRefreshToken()).await().indefinitely();
            currentTokens = tokens;
        }
        // Use tokens.getAccessToken() to configure MP RestClient Authorization header/etc
    }
}

1.1.4. トークンの注入

OidcClient を内部的に使用する Tokens を注入できます。Tokens を使用してアクセストークンを取得し、必要に応じて更新することができます。

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

import io.quarkus.oidc.client.Tokens;

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

    @Inject Tokens tokens;

    @GET
    public String getResponse() {
        //  Get the access token, which might have been refreshed.
        String accessToken = tokens.getAccessToken();
        // Use the access token to configure MP RestClient Authorization header/etc
    }
}

1.1.5. OidcClients の使用

io.quarkus.oidc.client.OidcClientsOidcClient のコンテナーです。デフォルトの OidcClient と、次のように設定できる名前付きクライアントが含まれています。

quarkus.oidc-client.client-enabled=false

quarkus.oidc-client.jwt-secret.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.jwt-secret.client-id=quarkus-app
quarkus.oidc-client.jwt-secret.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

この場合、デフォルトのクライアントは client-enabled=false プロパティーによって無効になります。jwt-secret クライアントには次のようにアクセスできます。

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

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;

@Path("/clients")
public class OidcClientResource {

    @Inject
    OidcClients clients;

    @GET
    public String getResponse() {
        OidcClient client = clients.getClient("jwt-secret");
        //Use this client to get the token
    }
}
注記

OIDC マルチテナンシー も使用しており、各 OIDC テナントに独自の関連付けたれた OidcClient がある場合は、Vert.x RoutingContext tenantId 属性を使用できます。以下に例を示します。

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

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.vertx.ext.web.RoutingContext;

@Path("/clients")
public class OidcClientResource {

    @Inject
    OidcClients clients;
    @Inject
    RoutingContext context;

    @GET
    public String getResponse() {
        String tenantId = context.get("tenantId");
        // named OIDC tenant and client configurations use the same key:
        OidcClient client = clients.getClient(tenantId);
        //Use this client to get the token
    }
}

必要に応じて、次のようにプログラムで新しい OidcClient を作成することもできます。

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

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.oidc.client.OidcClientConfig;

import io.smallrye.mutiny.Uni;

@Path("/clients")
public class OidcClientResource {

    @Inject
    OidcClients clients;

    @GET
    public String getResponse() {
        OidcClientConfig cfg = new OidcClientConfig();
        cfg.setId("myclient");
        cfg.setAuthServerUrl("http://localhost:8081/auth/realms/quarkus/");
        cfg.setClientId("quarkus");
        cfg.getCredentials().setSecret("secret");
        Uni<OidcClient> client = clients.newClient(cfg);
        //Use this client to get the token
    }
}

1.1.6. 名前付き OidcClient とトークンの注入

複数の OidcClient オブジェクトが設定されている場合は、OidcClients を使用する代わりに、追加の修飾子 @NamedOidcClient によって OidcClient 注入ターゲットを指定できます。

package io.quarkus.oidc.client;

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

@Path("/clients")
public class OidcClientResource {

    @Inject
    @NamedOidcClient("jwt-secret")
    OidcClient client;

    @GET
    public String getResponse() {
        //Use the client to get the token
    }
}

同じ修飾子を使用して、Tokens の注入に使用される OidcClient を指定できます。

@Provider
@Priority(Priorities.AUTHENTICATION)
@RequestScoped
public class OidcClientRequestCustomFilter implements ClientRequestFilter {

    @Inject
    @NamedOidcClient("jwt-secret")
    Tokens tokens;

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
    }
}

1.1.7. RestClient Reactive ClientFilter での OidcClient の使用

以下の Maven 依存関係を追加します。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-client-reactive-filter</artifactId>
</dependency>

io.quarkus:quarkus-oidc-client も導入されることに注意してください。

quarkus-oidc-client-reactive-filter エクステンションは、io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter を提供します。

これは、OidcClientRequestFilter と同様に機能します (MicroProfile RestClient クライアントフィルターで OidcClient を使用する を参照)。OidcClient を使用してアクセストークンを取得し、必要に応じて更新し、HTTP Authorization Bearer スキーム値として設定します。違いは、Reactive RestClient と連携し、トークンを取得または更新するときに現在の IO スレッドをブロックしない非ブロッキングクライアントフィルターを実装することです。

OidcClientRequestReactiveFilter は、IO スレッドのブロックを回避するために、最初のトークンの取得が実行されるまで遅延します。

io.quarkus.oidc.client.reactive.filter.OidcClientFilter または org.eclipse.microprofile.rest.client.annotation.RegisterProvider アノテーションを使用して、OidcClientRequestReactiveFilter を選択的に登録できます。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {

    @GET
    Uni<String> getUserName();
}

あるいは、以下のような場合もあります。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@RegisterProvider(OidcClientRequestReactiveFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    Uni<String> getUserName();
}

OidcClientRequestReactiveFilter はデフォルトの OidcClient を使用します。名前付き OidcClient は、quarkus.oidc-client-reactive-filter.client-name 設定プロパティーを使用して選択できます。@OidcClientFilter アノテーションの value 属性を設定して、OidcClient を選択することもできます。アノテーションを通じて設定されたクライアント名は、quarkus.oidc-client-reactive-filter.client-name 設定プロパティーよりも優先されます。たとえば、OIDC クライアント宣言という名前の こちらjwt-secret がある場合、このクライアントを次のように参照できます。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@OidcClientFilter("jwt-secret")
@Path("/")
public interface ProtectedResourceService {

    @GET
    Uni<String> getUserName();
}

1.1.8. RestClient ClientFilter での OidcClient の使用

以下の Maven 依存関係を追加します。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-client-filter</artifactId>
</dependency>

io.quarkus:quarkus-oidc-client も導入されることに注意してください。

quarkus-oidc-client-filter エクステンションは、OidcClient を使用してアクセストークンを取得し、必要に応じて更新し、HTTP Authorization Bearer スキーム値として設定する io.quarkus.oidc.client.filter.OidcClientRequestFilter Jakarta REST ClientRequestFilter を提供します。

デフォルトでは、このフィルターは、初期化時に OidcClient がアクセストークンと更新トークンの最初のペアを取得するようにします。アクセストークンの有効期間が短く、更新トークンが利用できない場合は、quarkus.oidc-client.early-tokens-acquisition=false を使用してトークンの取得を遅らせる必要があります。

io.quarkus.oidc.client.filter.OidcClientFilter または org.eclipse.microprofile.rest.client.annotation.RegisterProvider のいずれかのアノテーションを使用して、OidcClientRequestFilter を選択的に登録できます。

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;

@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

あるいは、以下のような場合もあります。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientRequestFilter;

@RegisterRestClient
@RegisterProvider(OidcClientRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

または、quarkus.oidc-client-filter.register-filter=true プロパティーが設定されていると、OidcClientRequestFilter はすべての MP Rest または Jakarta REST クライアントに自動的に登録できます。

OidcClientRequestFilter はデフォルトの OidcClient を使用します。名前付き OidcClient は、quarkus.oidc-client-filter.client-name 設定プロパティーを使用して選択できます。@OidcClientFilter アノテーションの value 属性を設定して、OidcClient を選択することもできます。アノテーションを通じて設定されたクライアント名は、quarkus.oidc-client-filter.client-name 設定プロパティーよりも優先されます。たとえば、OIDC クライアント宣言という名前の こちらjwt-secret がある場合、このクライアントを次のように参照できます。

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;

@RegisterRestClient
@OidcClientFilter("jwt-secret")
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

1.1.9. カスタム RestClient ClientFilter を使用する

必要に応じて、独自のカスタムフィルターを使用して Tokens を注入することもできます。

import io.quarkus.oidc.client.Tokens;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter implements ClientRequestFilter {

    @Inject
    Tokens tokens;

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
    }
}

Tokens プロデューサーはトークンを取得して更新し、カスタムフィルターはトークンの使用方法とタイミングを決定します。

名前付きの Tokens を注入することもできます。名前付きの OidcClient とトークンの注入 を参照してください。

1.1.10. アクセストークンの更新

OidcClientRequestReactiveFilterOidcClientRequestFilter、および Tokens プロデューサーは、更新トークンが利用可能な場合、現在の期限切れのアクセストークンを更新します。さらに、quarkus.oidc-client.refresh-token-time-skew プロパティーを使用して、アクセストークンをプリエンプティブに更新し、HTTP 401 エラーの原因となる可能性のある期限切れに近いアクセストークンの送信を回避することもできます。たとえば、このプロパティーが 3S に設定され、アクセストークンの有効期限が 3 秒未満の場合、このトークンは自動的に更新されます。

アクセストークンを更新する必要があるが、更新トークンが利用できない場合は、client_credentials などの設定された許可を使用して新しいトークンを取得しようとします。

一部の OpenID Connect プロバイダーは、client_credentials グラントのレスポンスでリフレッシュトークンを返しません。たとえば、Keycloak 12 以降では、client_credentials に対して更新トークンがデフォルトで返されなくなります。プロバイダーは、更新トークンの使用回数を制限する場合もあります。

1.1.11. アクセストークンの取り消し

Keycloak などの OpenId Connect プロバイダーがトークン失効エンドポイントをサポートしている場合は、OidcClient#revokeAccessToken を使用して現在のアクセストークンを取り消すことができます。失効エンドポイント URL は、トークン要求 URI と一緒に検出されるか、quarkus.oidc-client.revoke-path を使用して設定できます。

このトークンを REST クライアントで使用すると HTTP 401 ステータスコードで失敗した場合、またはアクセストークンがすでに長期間使用されていて更新したい場合は、アクセストークンの取り消しが必要になる場合があります。

これは、更新トークンを使用して、トークンの更新をリクエストすることによって実現できます。ただし、更新トークンが利用できない場合は、まず更新トークンを取り消してから新しいアクセストークンを要求することで更新できます。

1.1.12. OidcClient 認証

OidcClient は、client_credentials およびその他の許可要求が成功するために、OpenID Connect Provider に対して認証する必要があります。すべての OIDC クライアント認証 オプションがサポートされています。以下に例を示します。

client_secret_basic:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=mysecret

あるいは、以下のような場合もあります。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.client-secret.value=mysecret

または、CredentialsProvider から取得したシークレットを使用します。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app

# This key is used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc-client.credentials.client-secret.provider.key=mysecret-key
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.client-secret.provider.name=oidc-credentials-provider

client_secret_post:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.client-secret.value=mysecret
quarkus.oidc-client.credentials.client-secret.method=post

client_secret_jwt: 署名アルゴリズムは HS256 です。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

または、CredentialsProvider から取得したシークレットの場合、署名アルゴリズムは HS256 です。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app

# This is a key that will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc-client.credentials.jwt.secret-provider.key=mysecret-key
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider

PEM キーファイルを使用した private_key_jwt: 署名アルゴリズムは RS256 です。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem

キーストアファイルを含む private_key_jwt: 署名アルゴリズムは RS256 です。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-store-file=keystore.jks
quarkus.oidc-client.credentials.jwt.key-store-password=mypassword
quarkus.oidc-client.credentials.jwt.key-password=mykeypassword

# Private key alias inside the keystore
quarkus.oidc-client.credentials.jwt.key-id=mykeyAlias

client_secret_jwt または private_key_jwt 認証方法を使用すると、クライアントシークレットがネットワーク上に送信されなくなります。

1.1.12.1. 追加の JWT 認証オプション

client_secret_jwt または private_key_jwt のいずれかの認証方法を使用する場合は、JWT 署名アルゴリズム、キー識別子、audience、サブジェクト、発行者をカスタマイズできます。次に例を示します。

# private_key_jwt client authentication

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem

# This is a token key identifier 'kid' header - set it if your OpenID Connect provider requires it.
# Note that if the key is represented in a JSON Web Key (JWK) format with a `kid` property, then
# using 'quarkus.oidc-client.credentials.jwt.token-key-id' is unnecessary.
quarkus.oidc-client.credentials.jwt.token-key-id=mykey

# Use the RS512 signature algorithm instead of the default RS256
quarkus.oidc-client.credentials.jwt.signature-algorithm=RS512

# The token endpoint URL is the default audience value; use the base address URL instead:
quarkus.oidc-client.credentials.jwt.audience=${quarkus.oidc-client.auth-server-url}

# custom subject instead of the client ID:
quarkus.oidc-client.credentials.jwt.subject=custom-subject

# custom issuer instead of the client ID:
quarkus.oidc-client.credentials.jwt.issuer=custom-issuer
1.1.12.2. Apple POST JWT

Apple OpenID Connect プロバイダーは client_secret_post メソッドを使用します。ここで、シークレットは private_key_jwt 認証メソッドで生成された JWT ですが、Apple アカウント固有の発行者およびサブジェクトプロパティーを持ちます。

quarkus-oidc-client は非標準の client_secret_post_jwt 認証方式をサポートしており、次のように設定できます。

quarkus.oidc-client.auth-server-url=${apple.url}
quarkus.oidc-client.client-id=${apple.client-id}
quarkus.oidc-client.credentials.client-secret.method=post-jwt

quarkus.oidc-client.credentials.jwt.key-file=ecPrivateKey.pem
quarkus.oidc-client.credentials.jwt.signature-algorithm=ES256
quarkus.oidc-client.credentials.jwt.subject=${apple.subject}
quarkus.oidc-client.credentials.jwt.issuer=${apple.issuer}
1.1.12.3. 相互 TLS

一部の OpenID Connect Provider では、相互 TLS (mTLS) 認証プロセスの一部としてクライアントが認証されることが要求されます。

quarkus-oidc-client は、mTLS をサポートするために次のように設定できます。

quarkus.oidc-client.tls.verification=certificate-validation

# Keystore configuration
quarkus.oidc-client.tls.key-store-file=client-keystore.jks
quarkus.oidc-client.tls.key-store-password=${key-store-password}

# Add more keystore properties if needed:
#quarkus.oidc-client.tls.key-store-alias=keyAlias
#quarkus.oidc-client.tls.key-store-alias-password=keyAliasPassword

# Truststore configuration
quarkus.oidc-client.tls.trust-store-file=client-truststore.jks
quarkus.oidc-client.tls.trust-store-password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.oidc-client.tls.trust-store-alias=certAlias

1.1.13. テスト

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

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <scope>test</scope>
</dependency>
1.1.13.1. Wiremock

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

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock</artifactId>
    <scope>test</scope>
</dependency>

Wiremock ベースの QuarkusTestResourceLifecycleManager を記述します。以下に例を示します。

package io.quarkus.it.keycloak;

import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

import java.util.HashMap;
import java.util.Map;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.Options.ChunkedEncodingPolicy;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager {
    private WireMockServer server;

    @Override
    public Map<String, String> start() {

        server = new WireMockServer(wireMockConfig().dynamicPort().useChunkedTransferEncoding(ChunkedEncodingPolicy.NEVER));
        server.start();

        server.stubFor(WireMock.post("/tokens")
                .withRequestBody(matching("grant_type=password&username=alice&password=alice"))
                .willReturn(WireMock
                        .aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody(
                                "{\"access_token\":\"access_token_1\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));
        server.stubFor(WireMock.post("/tokens")
                .withRequestBody(matching("grant_type=refresh_token&refresh_token=refresh_token_1"))
                .willReturn(WireMock
                        .aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody(
                                "{\"access_token\":\"access_token_2\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));


        Map<String, String> conf = new HashMap<>();
        conf.put("keycloak.url", server.baseUrl());
        return conf;
    }

    @Override
    public synchronized void stop() {
        if (server != null) {
            server.stop();
            server = null;
        }
    }
}

REST テストエンドポイントを準備します。登録された OidcClient フィルターを使用して挿入された MP REST クライアントを使用するテストフロントエンドエンドポイントで、ダウンストリームエンドポイントを呼び出すことができます。このエンドポイントはトークンをエコーバックします。たとえば、main の Quarkus リポジトリーの integration-tests/oidc-client-wiremock を参照してください。

application.properties を設定します。以下に例を示します。

# Use the 'keycloak.url' property set by the test KeycloakRealmResourceManager
quarkus.oidc-client.auth-server-url=${keycloak.url}
quarkus.oidc-client.discovery-enabled=false
quarkus.oidc-client.token-path=/tokens
quarkus.oidc-client.client-id=quarkus-service-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

最後にテストコードを記述します。上記の Wiremock ベースのリソースを考えると、最初のテスト呼び出しは access_token_1 アクセストークンを返すはずですが、これは 4 秒後に期限切れになります。awaitility を使用して約 5 秒間待機すると、次のテスト呼び出しで access_token_2 アクセストークンが返され、期限切れの access_token_1 アクセストークンが更新されたことが確認されます。

1.1.13.2. Keycloak

Keycloak を使用する場合は、Keycloak の OpenID Connect Bearer トークン結合テスト セクションで説明されているのと同じアプローチを使用できます。

1.1.14. ログ内のエラーを確認する方法

トークンの取得と更新エラーの詳細を確認するには、io.quarkus.oidc.client.runtime.OidcClientImpl TRACE レベルのログ記録を有効にします。

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

OidcClient 初期化エラーの詳細を表示するには、io.quarkus.oidc.client.runtime.OidcClientRecorder TRACE レベルのログ記録を有効にします。

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

1.2. OIDC リクエストフィルター

1 つ以上の OidcRequestFilter 実装を登録することで、Quarkus から OIDC プロバイダーに対して行われた OIDC リクエストをフィルタリングできます。これにより、新しいリクエストヘッダーを更新または追加できます。たとえば、フィルターはリクエスト本文を分析し、そのダイジェストを新しいヘッダー値として追加できます。

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.core.http.HttpMethod;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;

@ApplicationScoped
@Unremovable
public class OidcRequestCustomizer implements OidcRequestFilter {

    @Override
    public void filter(HttpRequest<Buffer> request, Buffer buffer, OidcRequestContextProperties contextProperties) {
        HttpMethod method = request.method();
        String uri = request.uri();
        if (method == HttpMethod.POST && uri.endsWith("/service") && buffer != null) {
            request.putHeader("Digest", calculateDigest(buffer.toString()));
        }
    }

    private String calculateDigest(String bodyString) {
        // Apply the required digest algorithm to the body string
    }
}

1.3. トークン伝播リアクティブ

quarkus-oidc-token-propagation-reactive エクステンションは、認証情報の伝播を簡素化する RestEasy Reactive Client io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter を提供します。このクライアントは、現在アクティブなリクエストに存在する ベアラートークン、または 認可コードフローメカニズム から取得したトークンを、HTTP Authorization ヘッダーの Bearer スキーム値として伝播します。

io.quarkus.oidc.token.propagation.AccessToken または org.eclipse.microprofile.rest.client.annotation.RegisterProvider アノテーションのいずれかを使用して、AccessTokenRequestReactiveFilter を選択的に登録できます。次に例を示します。

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessToken;

@RegisterRestClient
@AccessToken
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

あるいは、以下のような場合もあります。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter;

@RegisterRestClient
@RegisterProvider(AccessTokenRequestReactiveFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

さらに、AccessTokenRequestReactiveFilter は、トークンを伝播する前に交換する必要がある複雑なアプリケーションをサポートできます。

Keycloak または トークン交換 トークングラントをサポートする別の OIDC プロバイダーを使用する場合は、次のように AccessTokenRequestReactiveFilter を設定してトークンを交換できます。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=exchange
quarkus.oidc-client.grant-options.exchange.audience=quarkus-app-exchange

quarkus.oidc-token-propagation.exchange-token=true 1
1
OidcClient 名が io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient アノテーション属性で設定されている場合、exchange-token 設定プロパティーは無視されることに注意してください。

注記: AccessTokenRequestReactiveFilterOidcClient を使用して現在のトークンを交換します。quarkus.oidc-client.grant-options.exchange を使用して、OpenID Connect Provider が期待する追加の交換プロパティーを設定できます。

現在のトークンを交換するために JWT ベアラートークングラント使用する必要がある Azure などのプロバイダーを使用する場合は、次のように AccessTokenRequestReactiveFilter を設定してトークンを交換できます。

quarkus.oidc-client.auth-server-url=${azure.provider.url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret

quarkus.oidc-client.grant.type=jwt
quarkus.oidc-client.grant-options.jwt.requested_token_use=on_behalf_of
quarkus.oidc-client.scopes=https://graph.microsoft.com/user.read,offline_access

quarkus.oidc-token-propagation-reactive.exchange-token=true

AccessTokenRequestReactiveFilter はデフォルトの OidcClient を使用します。名前付きの OidcClient は、quarkus.oidc-token-propagation-reactive.client-name 設定プロパティーまたは io.quarkus.oidc.token.propagation.AccessToken#exchangeTokenClient アノテーション属性を使用して選択できます。

1.4. トークンの伝播

quarkus-oidc-token-propagation エクステンションは、認証情報の伝播を簡素化する 2 つの Jakarta REST jakarta.ws.rs.client.ClientRequestFilter クラス実装を提供します。io.quarkus.oidc.token.propagation.AccessTokenRequestFilter は、現在アクティブなリクエストに存在する ベアラートークン、または 認可コードフローメカニズム から取得されたトークンを、HTTP Authorization ヘッダーの Bearer スキーム値として伝播します。io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter は同じ機能を提供しますが、さらに JWT トークンのサポートも提供します。

現在の認可コードフローアクセストークンを伝播する必要がある場合は、即時トークン伝播が適切に機能します。これは、(ID トークンではなく) コードフローアクセストークンが、現在認証されているユーザーに代わってリモートサービスにアクセスするために現在の Quarkus エンドポイントに伝播されることを意図したものであるためです。

ただし、エンドツーエンドの Bearer トークンの直接的な伝播は回避する必要があります。たとえば、Client → Service A → Service B の場合、Service BClient から Service A に送信されたトークンを受信します。このような場合、Service B はトークンが Service A から来たものか、Client から直接来たものか区別できません。トークンが Service A から送信されたことを Service B が確認するには、新しい発行者と audience クレームをアサートできる必要があります。

さらに、複雑なアプリケーションでは、トークンを伝播する前に交換または更新しないといけない場合があります。たとえば、Service AService B にアクセスする場合、アクセスコンテキストが異なる場合があります。この場合、Service A には、Service B にアクセスするための狭い範囲またはまったく異なる範囲のセットが付与される可能性があります。

次のセクションでは、AccessTokenRequestFilterJsonWebTokenRequestFilter がどのように役立つかを示します。

1.4.1. RestClient AccessTokenRequestFilter

AccessTokenRequestFilter はすべてのトークンを文字列として扱うため、JWT トークンと不透明トークンの両方で動作します。

io.quarkus.oidc.token.propagation.AccessToken または org.eclipse.microprofile.rest.client.annotation.RegisterProvider のいずれかを使用して、AccessTokenRequestFilter を選択的に登録できます。次に例を示します。

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessToken;

@RegisterRestClient
@AccessToken
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

あるいは、以下のような場合もあります。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessTokenRequestFilter;

@RegisterRestClient
@RegisterProvider(AccessTokenRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

または、quarkus.oidc-token-propagation.register-filter プロパティーが true に設定され、quarkus.oidc-token-propagation.json-web-token プロパティーが false (デフォルト値) に設定されている場合は、AccessTokenRequestFilter はすべての MP Rest または Jakarta REST クライアントに自動的に登録されます。

1.4.1.1. 伝播前のトークンの交換

現在のアクセストークンを伝播前に交換する必要があり、トークン交換 トークングラントをサポートする Keycloak またはその他の OpenID Connect Provider を使用する場合は、AccessTokenRequestFilter を次のように設定できます。

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=exchange
quarkus.oidc-client.grant-options.exchange.audience=quarkus-app-exchange

quarkus.oidc-token-propagation.exchange-token=true

現在のトークンを交換するために JWT ベアラートークングラント使用する必要がある Azure などのプロバイダーを使用する場合は、次のように AccessTokenRequestFilter を設定してトークンを交換できます。

quarkus.oidc-client.auth-server-url=${azure.provider.url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret

quarkus.oidc-client.grant.type=jwt
quarkus.oidc-client.grant-options.jwt.requested_token_use=on_behalf_of
quarkus.oidc-client.scopes=https://graph.microsoft.com/user.read,offline_access

quarkus.oidc-token-propagation.exchange-token=true

注記: AccessTokenRequestFilterOidcClient を使用して現在のトークンを交換します。quarkus.oidc-client.grant-options.exchange を使用して、OpenID Connect Provider が期待する追加の交換プロパティーを設定できます。

AccessTokenRequestFilter はデフォルトの OidcClient を使用します。名前付き OidcClient は、quarkus.oidc-token-propagation.client-name 設定プロパティーを使用して選択できます。

1.4.2. RestClient JsonWebTokenRequestFilter

Bearer JWT トークンを使用する場合、issueraudience などのトークンのクレームを変更したり、更新されたトークンを再度保護 (再署名など) したりできる場合は、JsonWebTokenRequestFilter を使用することを推奨します。注入された org.eclipse.microprofile.jwt.JsonWebToken が想定されるため、不透明なトークンでは機能しません。また、OpenID Connect Provider が Token Exchange プロトコルをサポートしている場合は、代わりに AccessTokenRequestFilter を使用することを推奨します。AccessTokenRequestFilter を使用すると、JWT と不透明ベアラートークンの両方を安全に交換できるためです。

JsonWebTokenRequestFilter を使用すると、Service A の実装で、注入された org.eclipse.microprofile.jwt.JsonWebToken を新しい issueraudience のクレーム値で簡単に更新し、更新されたトークンを新しい署名で再度保護できるようになります。唯一の難しいステップは、Service A に署名キーがあることを確認することです。署名キーは、安全なファイルシステムまたは Vault などのリモートの安全なストレージからプロビジョニングする必要があります。

io.quarkus.oidc.token.propagation.JsonWebToken または org.eclipse.microprofile.rest.client.annotation.RegisterProvider のいずれかを使用して、JsonWebTokenRequestFilter を選択的に登録できます。次に例を示します。

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.JsonWebToken;

@RegisterRestClient
@JsonWebToken
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

あるいは、以下のような場合もあります。

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter;

@RegisterRestClient
@RegisterProvider(JsonWebTokenRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

あるいは、quarkus.oidc-token-propagation.register-filter プロパティーと quarkus.oidc-token-propagation.json-web-token プロパティーの両方が true に設定されている場合、JsonWebTokenRequestFilter がすべての MicroProfile REST または Jakarta REST クライアントに自動的に登録できます。

1.4.2.1. 伝播前のトークンの更新

注入されたトークンの iss (issuer) または aud (audience) クレームを更新し、新しい署名で再度保護する必要がある場合は、次のように JsonWebTokenRequestFilter を設定できます。

quarkus.oidc-token-propagation.secure-json-web-token=true
smallrye.jwt.sign.key.location=/privateKey.pem
# Set a new issuer
smallrye.jwt.new-token.issuer=http://frontend-resource
# Set a new audience
smallrye.jwt.new-token.audience=http://downstream-resource
# Override the existing token issuer and audience claims if they are already set
smallrye.jwt.new-token.override-matching-claims=true

前述のように、Keycloak または Token Exchange プロトコルをサポートする OpenID Connect Provider を使用する場合は、AccessTokenRequestFilter を使用します。

1.4.3. テスト

OpenID Connect ベアラートークン結合テスト セクションの説明に従って、トークンを生成できます。REST テストエンドポイントを準備します。登録されたトークン伝播フィルターを備えた朝中された MP REST クライアントを使用するテストフロントエンドエンドポイントで、ダウンストリームエンドポイントを呼び出すことができます。たとえば、main Quarkus リポジトリーの integration-tests/oidc-token-propagation を参照してください。

1.5. トークン伝播リアクティブ

以下の Maven 依存関係を追加します。

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-token-propagation-reactive</artifactId>
</dependency>

quarkus-oidc-token-propagation-reactive エクステンションは、現在の Bearer または Authorization Code Flow アクセストークンを伝播するために使用できる io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter を提供します。

quarkus-oidc-token-propagation-reactive エクステンション (非リアクティブ quarkus-oidc-token-propagation エクステンションとは対照的に) は現在、伝播前のトークンの交換または再署名をサポートしていません。ただし、これらの機能は今後追加される可能性があります。

1.6. 参考資料

第2章 OpenID Connect クライアントとトークンの伝播クイックスタート

フィルター付きの OpenID Connect (OIDC) および OAuth2 クライアントを使用して、アプリケーションでアクセストークンを取得、更新、および伝播する方法を学習します。

Quarkus での OIDC ClientToken Propagation のサポートの詳細は、OpenID Connect (OIDC) および OAuth2 クライアントとフィルターのリファレンスガイド を参照してください。

ベアラートークン認可を使用してアプリケーションを保護するには、OpenID Connect (OIDC) ベアラートークン認証 ガイドを参照してください。

2.1. 前提条件

このガイドを完了するには、以下が必要です。

  • 約 15 分
  • IDE
  • JAV_HOME が適切に設定された状態でインストールされた JDK 17+
  • Apache Maven 3.9.6
  • 動作するコンテナーランタイム (Docker または Podman)
  • オプションで使用する場合は Quarkus CLI
  • ネイティブ実行可能ファイル (ネイティブコンテナービルドを使用する場合は Docker) をビルドする必要がある場合は、オプションで Mandrel または GraalVM がインストールされ、適切に設定されている
  • jq tool

2.2. アーキテクチャー

この例では、FrontendResourceProtectedResource という 2 つの Jakarta REST リソースを使用してアプリケーションがビルドされます。ここで、FrontendResource は、2 つの方法のいずれかを使用してアクセストークンを ProtectedResource に伝播します。

  • トークンを伝播する前に、OIDC トークン伝播リアクティブフィルターを使用してトークンを取得できます。
  • OIDC トークン伝播リアクティブフィルターを使用して、受信したアクセストークンを伝播できます。

FrontendResource には 4 つのエンドポイントがあります。

  • /frontend/user-name-with-oidc-client-token
  • /frontend/admin-name-with-oidc-client-token
  • /frontend/user-name-with-propagated-token
  • /frontend/admin-name-with-propagated-token

FrontendResource は、/frontend/user-name-with-oidc-client-token または /frontend/admin-name-with-oidc-client-token のいずれかが呼び出されたときに、OIDC トークン伝播 Reactive フィルターを備えた REST クライアントを使用して、ProtectedResource へのアクセストークンを取得して伝播します。また、FrontendResource は、/frontend/user-name-with-propagated-token または /frontend/admin-name-with-propagated-token のいずれかが呼び出されたときに OpenID Connect Token Propagation Reactive Filter を備えた REST Client を使用して、現在の受信アクセストークンを ProtectedResource に伝播します。

ProtectedResource には 2 つのエンドポイントがあります。

  • /protected/user-name
  • /protected/admin-name

両方のエンドポイントは、FrontendResource から ProtectedResource に伝播された受信アクセストークンから展開されたユーザー名を返します。これらのエンドポイントの唯一の違いは、/protected/user-name の呼び出しは現在のアクセストークンに user ロールがある場合にのみ許可され、/protected/admin-name の呼び出しは現在のアクセストークンに admin ロールがある場合にのみ許可されることです。

2.3. 解決方法

次のセクションの指示に従って、アプリケーションを段階的に作成することを推奨します。ただし、完成した例に直接進むこともできます。

Git リポジトリーをクローンします: git clone https://github.com/quarkusio/quarkus-quickstarts.git -b 3.8、または アーカイブ をダウンロードします。

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

2.4. Maven プロジェクトの作成

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

  • Quarkus CLI を使用:

    quarkus create app org.acme:security-openid-connect-client-quickstart \
        --extension='oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive' \
        --no-code
    cd security-openid-connect-client-quickstart

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

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

  • Maven を使用:

    mvn io.quarkus.platform:quarkus-maven-plugin:3.8.5:create \
        -DprojectGroupId=org.acme \
        -DprojectArtifactId=security-openid-connect-client-quickstart \
        -Dextensions='oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive' \
        -DnoCode
    cd security-openid-connect-client-quickstart

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

Windows ユーザーの場合:

  • cmd を使用する場合は、バックスラッシュ \ を使用せず、すべてを同じ行に記述してください。
  • Powershell を使用する場合は、-D パラメーターを二重引用符で囲みます (例: "-DprojectArtifactId=security-openid-connect-client-quickstart")。

このコマンドは、oidcoidc-client-reactive-filteroidc-token-propagation-reactive-filter、および resteasy-reactive エクステンションをインポートする Maven プロジェクトを生成します。

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

  • Quarkus CLI を使用:

    quarkus extension add oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive
  • Maven を使用:

    ./mvnw quarkus:add-extension -Dextensions='oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive'
  • Gradle を使用する場合:

    ./gradlew addExtension --extensions='oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive'

このコマンドは、ビルドファイルに次のエクステンションを追加します。

  • Maven を使用:

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-oidc</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-oidc-client-reactive-filter</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-oidc-token-propagation-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
  • Gradle を使用する場合:

    implementation("io.quarkus:quarkus-oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive")

2.5. アプリケーションの作成

まず、ProtectedResource を実装します。

package org.acme.security.openid.connect.client;

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;

import org.eclipse.microprofile.jwt.JsonWebToken;

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

    @Inject
    JsonWebToken principal;

    @GET
    @RolesAllowed("user")
    @Produces("text/plain")
    @Path("userName")
    public Uni<String> userName() {
        return Uni.createFrom().item(principal.getName());
    }

    @GET
    @RolesAllowed("admin")
    @Produces("text/plain")
    @Path("adminName")
    public Uni<String> adminName() {
        return Uni.createFrom().item(principal.getName());
    }
}

ProtectedResource は、userName() メソッドと adminName() メソッドの両方から名前を返します。名前は現在の JsonWebToken から展開されます。

次に、FrontendResourceProtectedResource を呼び出すために使用する 2 つの REST クライアント OidcClientRequestReactiveFilter および AccessTokenRequestReactiveFilter を追加します。

OidcClientRequestReactiveFilter REST クライアントを追加します。

package org.acme.security.openid.connect.client;

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

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@RegisterProvider(OidcClientRequestReactiveFilter.class)
@Path("/")
public interface RestClientWithOidcClientFilter {

    @GET
    @Produces("text/plain")
    @Path("userName")
    Uni<String> getUserName();

    @GET
    @Produces("text/plain")
    @Path("adminName")
    Uni<String> getAdminName();
}

RestClientWithOidcClientFilter インターフェイスは、トークンを取得して伝播するために OidcClientRequestReactiveFilter に依存します。

AccessTokenRequestReactiveFilter REST クライアントを追加します。

package org.acme.security.openid.connect.client;

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

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter;
import io.smallrye.mutiny.Uni;

@RegisterRestClient
@RegisterProvider(AccessTokenRequestReactiveFilter.class)
@Path("/")
public interface RestClientWithTokenPropagationFilter {

    @GET
    @Produces("text/plain")
    @Path("userName")
    Uni<String> getUserName();

    @GET
    @Produces("text/plain")
    @Path("adminName")
    Uni<String> getAdminName();
}

RestClientWithTokenPropagationFilter インターフェイスは、受信する既存のトークンを伝播するために AccessTokenRequestReactiveFilter に依存します。

RestClientWithOidcClientFilter インターフェイスと RestClientWithTokenPropagationFilter インターフェイスは、どちらも同じであることに注意してください。これは、同じ REST Client 上で OidcClientRequestReactiveFilter および AccessTokenRequestReactiveFilter を組み合わせると、両方のフィルターが相互に干渉する可能性があるため、副作用が発生するためです。たとえば、OidcClientRequestReactiveFilter は、AccessTokenRequestReactiveFilter によって伝播されたトークンをオーバーライドできます。また、伝播できるトークンがなく、代わりに OidcClientRequestReactiveFilter が新しいトークンを取得することが予想されるときにそれが呼び出されると、AccessTokenRequestReactiveFilter が失敗する可能性があります。

次に、FrontendResource を追加してアプリケーションの作成を完了します。

package org.acme.security.openid.connect.client;

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

import org.eclipse.microprofile.rest.client.inject.RestClient;

import io.smallrye.mutiny.Uni;

@Path("/frontend")
public class FrontendResource {
    @Inject
    @RestClient
    RestClientWithOidcClientFilter restClientWithOidcClientFilter;

    @Inject
    @RestClient
    RestClientWithTokenPropagationFilter restClientWithTokenPropagationFilter;

    @GET
    @Path("user-name-with-oidc-client-token")
    @Produces("text/plain")
    public Uni<String> getUserNameWithOidcClientToken() {
        return restClientWithOidcClientFilter.getUserName();
    }

    @GET
    @Path("admin-name-with-oidc-client-token")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithOidcClientToken() {
	    return restClientWithOidcClientFilter.getAdminName();
    }

    @GET
    @Path("user-name-with-propagated-token")
    @Produces("text/plain")
    public Uni<String> getUserNameWithPropagatedToken() {
        return restClientWithTokenPropagationFilter.getUserName();
    }

    @GET
    @Path("admin-name-with-propagated-token")
    @Produces("text/plain")
    public Uni<String> getAdminNameWithPropagatedToken() {
        return restClientWithTokenPropagationFilter.getAdminName();
    }
}

FrontendResource は、/frontend/user-name-with-oidc-client-token または /frontend/admin-name-with-oidc-client-token のいずれかが呼び出されたときに、OIDC トークン伝播 Reactive フィルターを備えた REST クライアントを使用して、ProtectedResource へのアクセストークンを取得して伝播します。また、FrontendResource は、/frontend/user-name-with-propagated-token または /frontend/admin-name-with-propagated-token のいずれかが呼び出されたときに OpenID Connect Token Propagation Reactive Filter を備えた REST Client を使用して、現在の受信アクセストークンを ProtectedResource に伝播します。

最後に、Jakarta REST ExceptionMapper を追加します。

package org.acme.security.openid.connect.client;

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.ClientWebApplicationException;

@Provider
public class FrontendExceptionMapper implements ExceptionMapper<ClientWebApplicationException> {

	@Override
	public Response toResponse(ClientWebApplicationException t) {
		return Response.status(t.getResponse().getStatus()).build();
	}

}

この例外マッパーは、トークンに予期されるロールがない場合に ProtectedResource403 を返すことをテスト中に検証するためにのみ追加されます。このマッパーがなければ、RESTEasy Reactive は、REST クライアント呼び出しから逃れた例外を 500 に正しく変換し、ProtectedResource などのダウンストリームリソースからの情報の漏洩を回避します。ただし、テストでは、500 が何らかの内部エラーではなく、認可例外によって発生したと断言することはできません。

2.6. アプリケーションの設定

コードを準備したら、アプリケーションを設定します。

# Configure OIDC

%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=secret

# Tell Dev Services for Keycloak to import the realm file
# This property is ineffective when running the application in JVM or Native modes but only in dev and test modes.

quarkus.keycloak.devservices.realm-path=quarkus-realm.json

# Configure OIDC Client

quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url}
quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

# Configure REST clients

%prod.port=8080
%dev.port=8080
%test.port=8081

org.acme.security.openid.connect.client.RestClientWithOidcClientFilter/mp-rest/url=http://localhost:${port}/protected
org.acme.security.openid.connect.client.RestClientWithTokenPropagationFilter/mp-rest/url=http://localhost:${port}/protected

この設定は Keycloak を参照します。Keycloak は、ProtectedResource によって受信アクセストークンを検証するために使用され、OidcClient によって password グラントを使用してユーザー alice のトークンを取得するために使用されます。両方の REST クライアントは `ProtectedResource の HTTP アドレスを指します。

注記

quarkus.oidc.auth-server-url%prod. プロファイル接頭辞を追加すると、アプリケーションが開発モードまたはテストモードで実行されるときに、Dev Services for Keycloak によってコンテナーが起動されるようになります。詳細は、開発モードでのアプリケーションの実行 セクションを参照してください。

2.7. Keycloak サーバーの起動と設定

注記

アプリケーションを開発モードまたはテストモードで実行するときは、Keycloak サーバーを起動しないでください。Dev Services for Keycloak がコンテナーを起動します。詳細は、開発モードでのアプリケーションの実行 セクションを参照してください。レルム設定ファイルtarget/classes ディレクトリーのクラスパスに配置するようにしてください。この配置により、ファイルが開発モードで自動的にインポートされるようになります。ただし、すでに 完全なソリューション をビルドしている場合は、ビルドプロセスによってすでに実行されているため、レルムファイルをクラスパスに追加する必要はありません。

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

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

{keycloak.version}24.0.0 以降に設定します。

Keycloak サーバーには localhost:8180 からアクセスできます。

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

新しいレルムを作成するには、レルム設定ファイル をインポートします。詳細は、新しいレルムを作成する 方法に関する Keycloak のドキュメントを参照してください。

この quarkus レルムファイルは、frontend クライアントと、alice および admin ユーザーを追加します。alice には user ロールがあります。admin にはuseradmin の両方のロールがあります。

2.8. 開発モードでのアプリケーションの実行

開発モードでアプリケーションを実行するには、次を使用します。

  • Quarkus CLI を使用:

    quarkus dev
  • Maven を使用:

    ./mvnw quarkus:dev
  • Gradle を使用する場合:

    ./gradlew --console=plain quarkusDev

Dev Services for Keycloak が Keycloak コンテナーを起動し、quarkus-realm.json をインポートします。

/q/dev-ui で利用可能な Dev UI を開き、OpenID Connect Dev UI カードの Provider: Keycloak リンクをクリックします。

求められたら、OpenID Connect Dev UI によって提供される Single Page Application にログインします。

  • パスワード alice を使用し、alice としてログインします。このユーザーには user ロールがあります。

    • /frontend/user-name-with-propagated-token にアクセスすると、200 が返されます。
    • /frontend/admin-name-with-propagated-token にアクセスすると、403 が返されます。
  • ログアウトし、パスワード admin を使用して admin として再度ログインします。このユーザーには adminuser の両方のロールがあります。

    • /frontend/user-name-with-propagated-token にアクセスすると、200 が返されます。
    • /frontend/admin-name-with-propagated-token にアクセスすると、200 が返されます。

この場合、FrontendResource が OpenID Connect Dev UI からアクセストークンを伝播できることをテストしています。

2.9. JVM モードでのアプリケーションの実行

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

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

  • Quarkus CLI を使用:

    quarkus build
  • Maven を使用:

    ./mvnw install
  • Gradle を使用する場合:

    ./gradlew build

次に、実行します。

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

2.10. ネイティブモードでアプリケーションの実行

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

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

コンパイルには時間がかかるため、この手順はデフォルトでオフになっています。再度ビルドするには、native プロファイルを有効にします。

  • Quarkus CLI を使用:

    quarkus build --native
  • Maven を使用:

    ./mvnw install -Dnative
  • Gradle を使用する場合:

    ./gradlew build -Dquarkus.package.type=native

しばらくしてビルドが完了すると、ネイティブバイナリーを直接実行できます。

./target/security-openid-connect-quickstart-1.0.0-SNAPSHOT-runner

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

開発モードでアプリケーションをテストする方法の詳細は、前述の 開発モードでのアプリケーションの実行 セクションを参照してください。

curl を使用して、JVM モードまたはネイティブモードで起動されたアプリケーションをテストできます。

alice のアクセストークンを取得します。

export access_token=$(\
    curl --insecure -X POST http://localhost:8180/realms/quarkus/protocol/openid-connect/token \
    --user backend-service:secret \
    -H 'content-type: application/x-www-form-urlencoded' \
    -d 'username=alice&password=alice&grant_type=password' | jq --raw-output '.access_token' \
 )

次に、このトークンを使用して、/frontend/user-name-with-propagated-token および /frontend/admin-name-with-propagated-tokenを呼び出します。

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

このコマンドは、ステータスコード 200 と名前 alice を返します。

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

対照的に、このコマンドは 403 を返します。alice には user ロールのみがあることを思い出してください。

次に、admin のアクセストークンを取得します。

export access_token=$(\
    curl --insecure -X POST http://localhost:8180/realms/quarkus/protocol/openid-connect/token \
    --user backend-service:secret \
    -H 'content-type: application/x-www-form-urlencoded' \
    -d 'username=admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \
 )

このトークンを使用して /frontend/user-name-with-propagated-token を呼び出します。

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

このコマンドは、ステータスコード 200 と名前 admin を返します。

次に、このトークンを使用して /frontend/admin-name-with-propagated-token を呼び出します。

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-propagated-token \
  -H "Authorization: Bearer "$access_token

adminuseradmin の両方のロールがあるため、このコマンドは、200 ステータスコードと名前 admin も返します。

ここで、既存のトークンを伝播せず、OidcClient を使用してトークンを取得および伝播する FrontendResource メソッドを確認します。すでに示したように、OidcClientalice ユーザーのトークンを取得するように設定されているため、次のようになります。

curl -i -X GET \
  http://localhost:8080/frontend/user-name-with-oidc-client-token

このコマンドは、ステータスコード 200 と名前 alice を返します。

curl -i -X GET \
  http://localhost:8080/frontend/admin-name-with-oidc-client-token

前のコマンドとは対照的に、このコマンドは 403 ステータスコードを返します。

2.12. 参考資料

法律上の通知

Copyright © 2024 Red Hat, Inc.
The text of and illustrations in this document are licensed by Red Hat under a Creative Commons Attribution–Share Alike 3.0 Unported license ("CC-BY-SA"). An explanation of CC-BY-SA is available at http://creativecommons.org/licenses/by-sa/3.0/. In accordance with CC-BY-SA, if you distribute this document or an adaptation of it, you must provide the URL for the original version.
Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.
Red Hat, Red Hat Enterprise Linux, the Shadowman logo, the Red Hat logo, JBoss, OpenShift, Fedora, the Infinity logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.
Linux® is the registered trademark of Linus Torvalds in the United States and other countries.
Java® is a registered trademark of Oracle and/or its affiliates.
XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.
MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.
Node.js® is an official trademark of Joyent. Red Hat is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.
The OpenStack® Word Mark and OpenStack logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.
All other trademarks are the property of their respective owners.
Red Hat logoGithubRedditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

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

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

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

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

会社概要

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

© 2024 Red Hat, Inc.