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


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

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

  • quarkus-oidc-clientquarkus-rest-client-oidc-filter、および quarkus-resteasy-client-oidc-filter エクステンション。OpenID Connect および Keycloak などの OAuth 2.0 準拠の認可サーバーからアクセストークンを取得および更新するために使用します。
  • quarkus-rest-client-oidc-token-propagation および quarkus-resteasy-client-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 グラントの使用が必要になることがあります。詳細は、Quarkus REST のトークン伝播 および RESTEasy Classic のトークン伝播 を参照してください。

何らかの理由で、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 を直接使用してアクセストークンを取得し、それを Bearer スキーム値として HTTP Authorization ヘッダーに設定できます。

たとえば、ユーザー名を返すマイクロサービスに Quarkus エンドポイントがアクセスする必要があるとします。まず、REST クライアントを作成します。

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

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

import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

@RegisterRestClient
@Path("/")
public interface RestClientWithTokenHeaderParam {

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

次に、OidcClient を使用してトークンを取得し、伝播します。

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

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

import io.quarkus.oidc.client.runtime.TokensHelper;
import io.quarkus.oidc.client.OidcClient;

import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

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

    @Inject
    OidcClient client;
    TokensHelper tokenHelper = new TokensHelper(); 1

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient;

    @GET
    @Path("user-name")
    @Produces("text/plain")
    public Uni<String> getUserName() {
    	return tokenHelper.getTokens(client).onItem()
        		.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
    }
}
1
io.quarkus.oidc.client.runtime.TokensHelper は、アクセストークンの取得と更新を管理します。

1.1.4. トークンの注入

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

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

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 org.eclipse.microprofile.rest.client.inject.RestClient;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.oidc.client.runtime.TokensHelper;

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

    @Inject
    OidcClients clients;
    TokensHelper tokenHelper = new TokensHelper();

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient; 1

    @GET
    @Path("user-name")
    @Produces("text/plain")
    public Uni<String> getUserName() {
    	OidcClient client = clients.getClient("jwt-secret");
    	return tokenHelper.getTokens(client).onItem()
        		.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
    }
}
1
OidcClient の直接使用 セクションの RestClientWithTokenHeaderParam 宣言を参照してください。
注記

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

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("tenant-id");
        // named OIDC tenant and client configurations use the same key:
        OidcClient client = clients.getClient(tenantId);
        //Use this client to get the token
    }
}

プログラムで新しい OidcClient を作成することもできます。たとえば、起動時に OidcClient を作成する必要があるとします。

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

import java.util.Map;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.runtime.OidcClientConfig;
import io.quarkus.oidc.client.runtime.OidcClientConfig.Grant.Type;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;

@ApplicationScoped
public class OidcClientCreator {

    @Inject
    OidcClients oidcClients;
    @ConfigProperty(name = "quarkus.oidc.auth-server-url")
    String oidcProviderAddress;

    private volatile OidcClient oidcClient;

    public void startup(@Observes StartupEvent event) {
    	createOidcClient().subscribe().with(client -> {oidcClient = client;});
    }

    public OidcClient getOidcClient() {
        return oidcClient;
    }

    private Uni<OidcClient> createOidcClient() {
        OidcClientConfig cfg = OidcClientConfig
            .authServerUrl(oidcProviderAddress)
            .id("myclient")
            .clientId("backend-service")
            .credentials("secret")
            .grant(Type.PASSWORD)
            .grantOptions("password", Map.of("username", "alice", "password", "alice"))
            .build();
        return oidcClients.newClient(cfg);
    }
}

このクライアントは次のように使用できます。

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

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.runtime.TokensHelper;

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

    @Inject
    OidcClientCreator oidcClientCreator;
    TokensHelper tokenHelper = new TokensHelper();

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient; 1

    @GET
    @Path("user-name")
    @Produces("text/plain")
    public Uni<String> getUserName() {
    	return tokenHelper.getTokens(oidcClientCreator.getOidcClient()).onItem()
        		.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
    }
}
1
OidcClient の直接使用 セクションの RestClientWithTokenHeaderParam 宣言を参照してください。

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

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

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

import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.NamedOidcClient;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.runtime.TokensHelper;

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

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

    TokensHelper tokenHelper = new TokensHelper();

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient; 1

    @GET
    @Path("user-name")
    @Produces("text/plain")
    public Uni<String> getUserName() {
    	return tokenHelper.getTokens(client).onItem()
        		.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
    }
}
1
OidcClient の直接使用 セクションの RestClientWithTokenHeaderParam 宣言を参照してください。

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

import java.io.IOException;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.ext.Provider;

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

@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-rest-client-oidc-filter</artifactId>
</dependency>
注記

io.quarkus:quarkus-oidc-client も追加されます。

quarkus-rest-client-oidc-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.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@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;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

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

    @GET
    Uni<String> getUserName();
}

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

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@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-resteasy-client-oidc-filter</artifactId>
</dependency>
注記

io.quarkus:quarkus-oidc-client も追加されます。

quarkus-resteasy-client-oidc-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;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@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;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

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

    @GET
    String getUserName();
}

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

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

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

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

    @GET
    String getUserName();
}

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

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

import java.io.IOException;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.ext.Provider;
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
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# 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
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider

PEM キーが application.properties にインライン化され、署名アルゴリズムが RS256 である private_key_jwt:

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=Base64-encoded private key representation

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.pkcs12
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. JWT ベアラー

RFC7523 では、JWT ベアラートークンを使用してクライアントを認証する方法について説明しています。詳細は、Using JWTs for Client Authentication セクションを参照してください。

有効にするには、以下を実行します。

quarkus.oidc-client.auth-server-url=${auth-server-url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.source=bearer

次に、JWT ベアラートークンを OIDC クライアントに client_assertion パラメーターとして提供する必要があります。

Quarkus はファイルシステムから JWT ベアラートークンをロードすることができます。たとえば、Kubernetes では、サービスアカウントトークンのプロジェクションを /var/run/secrets/tokens パスにマウントできます。後は JWT ベアラートークンパスを次のように設定するだけです。

quarkus.oidc-client.credentials.jwt.token-path=/var/run/secrets/tokens 1
1
JWT ベアラートークンへのパス。Quarkus はファイルシステムから新しいトークンをロードします。トークンの有効期限が切れるとそれを再ロードします。

他の選択肢としては、追加のグラントパラメーターを受け入れるトークンを取得または更新するための OidcClient メソッド (例: oidcClient.getTokens(Map.of("client_assertion", "ey…​"))) 使用する方法があります。

OIDC クライアントフィルターを使用する場合は、このアサーションを提供するカスタムフィルターを登録する必要があります。

以下は、Quarkus REST (旧称 RESTEasy Reactive) カスタムフィルターの例です。

package io.quarkus.it.keycloak;

import java.util.Map;

import io.quarkus.oidc.client.reactive.filter.runtime.AbstractOidcClientRequestReactiveFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;

@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter extends AbstractOidcClientRequestReactiveFilter {

    @Override
    protected Map<String, String> additionalParameters() {
        return Map.of(OidcConstants.CLIENT_ASSERTION, "ey...");
    }
}

以下は、RESTEasy Classic カスタムフィルターの例です。

package io.quarkus.it.keycloak;

import java.util.Map;

import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;

@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter extends AbstractOidcClientRequestFilter {

    @Override
    protected Map<String, String> additionalParameters() {
        return Map.of(OidcConstants.CLIENT_ASSERTION, "ey...");
    }
}

1.1.12.3. 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.4. 相互 TLS

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

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

quarkus.oidc-client.tls.tls-configuration-name=oidc-client

# configure hostname verification if necessary
#quarkus.tls.oidc-client.hostname-verification-algorithm=NONE

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

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

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

1.1.13. OIDC Client SPI

カスタムのエクステンションが、OIDC クライアントでサポートされているいずれかの OIDC トークングラントを使用して OIDC トークンを取得する必要がある場合、そのエクステンションで OIDC Client SPI を利用すれば、OIDC クライアント自体にアクセストークンを必要に応じて取得および更新させることができます。

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

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

次に、必要に応じて io.quarkus.oidc.client.spi.TokenProvider CDI Bean を使用するようにエクステンションを更新します。次に例を示します。

package org.acme.extension;

import jakarta.inject.Inject;
import io.quarkus.oidc.client.spi.TokenProvider;

public class ExtensionOAuth2Support {

   @Inject
   TokenProvider tokenProvider;

   public Uni<String> getAccessToken() {
       return tokenProvider.getAccessToken();
   }
}

現在、io.quarkus.oidc.client.spi.TokenProvider は、デフォルトの OIDC クライアントでのみ使用できます。カスタムエクステンションが複数の名前付き OIDC クライアントを認識することはほとんどないためです。

1.1.14. テスト

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

<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.14.1. Wiremock

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

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock</artifactId>
    <scope>test</scope>
    <version>${wiremock.version}</version> 1
</dependency>
1
適切な Wiremock バージョンを使用してください。利用可能なすべてのバージョンを こちら で確認できます。

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:replaced-by-test-resource}
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.14.2. Keycloak

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

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

トークンの取得と更新エラーの詳細を確認するには、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
Red Hat logoGithubRedditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

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

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

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

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

会社概要

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

© 2024 Red Hat, Inc.