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

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

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

これには、以下が含まれます。

  • Using quarkus-oidc-client, quarkus-oidc-client-reactive-filter and quarkus-oidc-client-filter extensions to acquire and refresh access tokens from OpenID Connect and OAuth 2.0 compliant Authorization Servers such as Keycloak.

  • Using quarkus-oidc-token-propagation-reactive and quarkus-oidc-token-propagation extensions to propagate the current Bearer or Authorization Code Flow access tokens.

これらのエクステンションで管理されているアクセストークンをHTTP Authorization ベアラートークンとして使用して、リモートサービスにアクセスすることができます。

OidcClient

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

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

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

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

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

デフォルトでは、トークンのエンドポイントアドレスは、設定された 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 の設定は必要ありません。

サポートされるトークングラント

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

クライアントクレデンシャル・グラント

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

パスワード・グラント

OidcClientpassword グラントを使用するように設定する方法は以下の通りです。

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 という設定プレフィックスを使用して、クライアント認証のグラントをカスタマイズする方法と同様に、カスタマイズすることが可能です。

その他のグラント

OidcClient は設定にはない特別な入力パラメーターを必要とするグラントを使用して、トークンの取得を支援することもできます。これらのグラントとは、 refresh_token (外部リフレッシュトークン付き)、 authorization_code 、および現在のアクセストークンを交換するために使用できる 2 つのグラント urn:ietf:params:oauth:grant-type:token-exchange および `urn:ietf:params:oauth:grant-type:jwt-bearer`です。

アクセストークンを取得する必要があり、既存のリフレッシュトークンを現在の Quarkus エンドポイントに POST している場合は、 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 filterにおけるトークン伝搬in MicroProfile RestClient filterにおけるトークン伝播 を参照してください。

何らかの理由で 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 メソッドで追加プロパティーの Map を受け取り、現在の coderedirect_uri パラメーターを渡して認可コードとトークンを交換することが可能です。

OidcClienturn: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 メソッドを使用して追加プロパティーの Map を受け入れ、 auth_req_id パラメーターを渡してトークン認可コードを交換できます。

グラントスコープ

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

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

トークンの注入

内部で 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
    }
}

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

If you also use OIDC multitenancy, and each OIDC tenant has its own associated OidcClient, you can use a Vert.x RoutingContext tenantId attribute. For example:

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

If you need, you can also create a new OidcClient programmatically like this:

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

名前の付いた OidcClient とトークンの注入

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

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

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 を提供します。

It works similarly to the way OidcClientRequestFilter does (see Use OidcClient in MicroProfile RestClient client filter) - it uses OidcClient to acquire the access token, refresh it if needed, and set it as an HTTP Authorization Bearer scheme value. The difference is that it works with Reactive RestClient and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens.

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

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

or

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 uses a default OidcClient by default. A named OidcClient can be selected with a quarkus.oidc-client-reactive-filter.client-name configuration property. You can also select OidcClient by setting the value attribute of the @OidcClientFilter annotation. The client name set through annotation has higher priority than the quarkus.oidc-client-reactive-filter.client-name configuration property. For example, given this jwt-secret named OIDC client declaration, you can refer to this client like this:

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

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 エクステンションは、 io.quarkus.oidc.client.filter.OidcClientRequestFilter Jakarta REST ClientRequestFilter を提供し、 OidcClient を使用してアクセストークンを取得し、必要に応じてリフレッシュし、HTTP Authorization Bearer スキーム値として設定します。

デフォルトでは、このフィルタは初期化時に 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();
}

or

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

Alternatively, OidcClientRequestFilter can be registered automatically with all MP Rest or Jakarta REST clients if the quarkus.oidc-client-filter.register-filter=true property is set.

OidcClientRequestFilter uses a default OidcClient by default. A named OidcClient can be selected with a quarkus.oidc-client-filter.client-name configuration property. You can also select OidcClient by setting the value attribute of the @OidcClientFilter annotation. The client name set through annotation has higher priority than the quarkus.oidc-client-filter.client-name configuration property. For example, given this jwt-secret named OIDC client declaration, you can refer to this client like this:

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

カスタムの 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 を挿入することもできます。Inject named OidcClient and Tokens を参照してください。

アクセストークンを更新する

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

アクセストークンの更新が必要なのにリフレッシュトークンがない場合は、 client_credentials のように設定されたグラントを使って新しいトークンの取得を試みます。

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

アクセストークンを失効する

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

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

リフレッシュトークンを使用してトークンのリフレッシュを要求することができます。リフレッシュトークンが利用できない場合は、まずそれを失効させてから、新しいアクセストークンをリクエストすることで更新できます。

OidcClient 認証

OidcClient は、 client_credentials および他のグラントリクエストが成功するために OpenID Connect プロバイダーに対して認証する必要があります。 OIDC Client Authentication オプションはすべてサポートされています。以下に例を示します。

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

or

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.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 認証方法を使用することで、クライアントシークレットが漏れることはありません。

JWT認証の追加オプション

client_secret_jwtprivate_key_jwt のいずれかの認証方法を使用する場合、JWT 署名アルゴリズム、鍵識別子、オーディエンス、サブジェクト、発行者などをカスタマイズすることが可能です。

# 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

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}

相互 TLS

一部の OpenID Connect プロバイダーでは、相互 TLS (mTLS) 認証プロセスの一部としてクライアントを認証する必要がある場合があります。

mTLS をサポートする為に quarkus-oidc-client は以下のように設定出来ます :

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

テスト

テストプロジェクトに以下の依存関係を追加することから始めます。

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

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 テストエンドポイントを用意します。注入された MP REST クライアントを登録された OidcClient フィルターで使用するテスト用フロントエンドエンドポイントが、トークンをエコーバックするダウンストリームエンドポイントを呼び出すことができます。例として、 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 アクセストークンが更新されたことが確認できます。

Keycloak

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

ログでエラーを確認する方法

トークン取得および更新エラーの詳細を確認するには、 io.quarkus.oidc.client.runtime.OidcClientImplTRACE レベルのロギングを有効にしてください。

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.OidcClientRecorderTRACE レベルのログを有効にしてください。

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

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

Token Propagation Reactive

The quarkus-oidc-token-propagation-reactive extension provides a RestEasy Reactive Client, io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter, that simplifies the propagation of authentication information. This client propagates the bearer token present in the currently active request or the token acquired from the authorization code flow mechanism as the HTTP Authorization header’s Bearer scheme value.

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

or

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 または Token Exchange トークングラントをサポートする別の 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 プロバイダーが期待する追加の交換プロパティーを設定できます。

Azure のように、JWTベアラートークングラント を使用して現在のトークンを交換する 必要がある プロバイダーを使用する場合、 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 アノテーション属性を使用して選択できます。

トークンの伝播

The quarkus-oidc-token-propagation extension provides two Jakarta REST jakarta.ws.rs.client.ClientRequestFilter class implementations that simplify the propagation of authentication information. io.quarkus.oidc.token.propagation.AccessTokenRequestFilter propagates the Bearer token present in the current active request or the token acquired from the Authorization code flow mechanism, as the HTTP Authorization header’s Bearer scheme value. The io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter provides the same functionality but, in addition, provides support for JWT tokens.

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

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

さらに、複雑なアプリケーションでは、トークンを伝搬する前に交換または更新する必要がある場合があります。たとえば、 Service AService B にアクセスするとき、アクセスコンテキストが異なる場合があります。この例では、 Service B にアクセスするために Service A に付与されるスコープは、範囲が狭いか、完全に異なる場合があります。

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

RestClient AccessTokenRequestFilter

AccessTokenRequestFilter は、すべてのトークンをStringとして扱うため、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();
}

or

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 に設定されている場合(デフォルト値です)、すべての MP Rest または Jakarta REST クライアントで AccessTokenRequestFilter を自動的に登録することができます。

伝播前にトークンを交換する

現在のアクセストークンを伝播する前に交換する必要があり、かつ Keycloak やその他の Token Exchange トークングラントをサポートする 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

Azure のように、 JWTベアラートークングラント を使用して現在のトークンを交換する 必要がある プロバイダーを使用する場合、 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 プロバイダーが期待する追加の交換プロパティーを設定できます。

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

RestClient JsonWebTokenRequestFilter

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

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

or

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-filterquarkus.oidc-token-propagation.json-web-token の両方のプロパティを true に設定すると、すべての MicroProfile REST または Jakarta REST クライアントで JsonWebTokenRequestFilter が自動的に登録されます。

伝播前にトークンを更新する

注入されたトークンの 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 またはトークン交換プロトコルをサポートする OpenID Connect プロバイダーを使用する場合は、 AccessTokenRequestFilter を使用します。

テスト

You can generate the tokens as described in OpenID Connect Bearer Token Integration testing section. Prepare the REST test endpoints. You can have the test front-end endpoint, which uses the injected MP REST client with a registered token propagation filter, call the downstream endpoint. For example, see the integration-tests/oidc-token-propagation in the main Quarkus repository.

Token Propagation Reactive

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

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

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

The quarkus-oidc-token-propagation-reactive extension (as opposed to the non-reactive quarkus-oidc-token-propagation extension) does not currently support the exchanging or resigning of the tokens before the propagation. However, these features might be added in the future.

GraphQL クライアントインテグレーション

quarkus-oidc-client-graphql エクステンションは、REST クライアントで使用されるアプローチと並行して、OIDC クライアントを GraphQL クライアント に統合する方法を提供します。 このエクステンションがアクティブな場合、プロパティーを通じて設定された GraphQL クライアント (ビルダーによってプログラム的にではなく) は OIDC クライアントを使用してアクセストークンを取得して Authorization ヘッダー値として設定します。 この OIDC クライアントは期限切れのアクセストークンも更新します。

以下のように、GraphQL クライアントが使用する OIDC クライアントを設定するには、 quarkus.oidc-client-graphql.client-name プロパティーが指定された設定済みの OIDC クライアントの 1 つを選択します。

quarkus.oidc-client-graphql.client-name=oidc-client-for-graphql

# OIDC クライアント自体の宣言例
quarkus.oidc-client.oidc-client-for-graphql.auth-server-url=${keycloak.url}
quarkus.oidc-client.oidc-client-for-graphql.grant.type=password
quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.username=${username}
quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.password=${password}
quarkus.oidc-client.oidc-client-for-graphql.client-id=${quarkus.oidc.client-id}
quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.value=${keycloak.credentials.secret}
quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.method=POST
`quarkus.oidc-client-graphql.client-name`プロパティーを指定しない場合は、 GraphQL クライアントは、デフォルトの OIDC クライアント (明示的な名前なし) を使用します。

以下のように、特に型安全な GraphQL クライアントの場合、 GraphQLClientApi インターフェイスに @io.quarkus.oidc.client.filter.OidcClientFilter のアノテーションを付けることで、これをクライアントごとにオーバーライドできます。

@GraphQLClientApi(configKey = "order-client")
@OidcClientFilter("oidc-client-for-graphql")
public interface OrdersGraphQLClient {
    // Queries, mutations, and subscriptions go here.
}

これをプログラムで作成された GraphQL クライアントで使用できるようにするために、 両方のビルダー (VertxDynamicGraphQLClientBuilderVertxTypesafeGraphQLClientBuilder) は dynamicHeader(String, Uni<String>) メソッドを含んでおり、変更される可能性のあるヘッダーをプラグインできます。 OIDC クライアントを接続するには、以下を使用します。

@Inject
OidcClients oidcClients;

VertxTypesafeGraphQLClientBuilder builder = ....;
Uni<String> tokenUni = oidcClients.getClient("OIDC_CLIENT_NAME")
    .getTokens().map(t -> "Bearer " + t.getAccessToken());
builder.dynamicHeader("Authorization", tokenUni);
VertxDynamicGraphQLClient client = builder.build();

関連コンテンツ