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

OpenID Connectクライアントとトークン伝搬クイックスタート

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

This approach uses an OIDC token propagation Reactive filter to propagate the incoming bearer access tokens.

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

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

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.9.6

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

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

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

  • jq tool

アーキテクチャ

In this example, an application is built with two Jakarta REST resources, FrontendResource and ProtectedResource. Here, FrontendResource uses one of two methods to propagate access tokens to ProtectedResource:

  • It can get a token by using an OIDC token propagation Reactive filter before propagating it.

  • It can use an OIDC token propagation Reactive filter to propagate the incoming access token.

FrontendResource has four endpoints:

  • /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 uses a REST Client with an OIDC token propagation Reactive filter to get and propagate an access token to ProtectedResource when either /frontend/user-name-with-oidc-client-token or /frontend/admin-name-with-oidc-client-token is called. Also, FrontendResource uses a REST Client with OpenID Connect Token Propagation Reactive Filter to propagate the current incoming access token to ProtectedResource when either /frontend/user-name-with-propagated-token or /frontend/admin-name-with-propagated-token is called.

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

  • /protected/user-name

  • /protected/admin-name

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

ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。

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

解決策は ` ディレクトリー にあります。

Mavenプロジェクトの作成

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

コマンドラインインタフェース
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.6.1: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"

This command generates a Maven project, importing the oidc, oidc-client-reactive-filter, oidc-token-propagation-reactive-filter, and resteasy-reactive extensions.

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

コマンドラインインタフェース
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'

This command adds the following extensions to your build file:

pom.xml
<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>
build.gradle
implementation("io.quarkus:quarkus-oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive")

アプリケーションの記述

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

ProtectedResourceuserName()adminName() の両方のメソッドから名前を返します。この名前は、現在の JsonWebToken から抽出されたものです。

Next, add two REST clients, OidcClientRequestReactiveFilter and AccessTokenRequestReactiveFilter, which FrontendResource uses to call ProtectedResource.

Add the OidcClientRequestReactiveFilter REST Client:

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

The RestClientWithOidcClientFilter interface depends on OidcClientRequestReactiveFilter to get and propagate the tokens.

Add the AccessTokenRequestReactiveFilter REST Client:

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

The RestClientWithTokenPropagationFilter interface depends on AccessTokenRequestReactiveFilter to propagate the incoming already-existing tokens.

Note that both RestClientWithOidcClientFilter and RestClientWithTokenPropagationFilter interfaces are the same. This is because combining OidcClientRequestReactiveFilter and AccessTokenRequestReactiveFilter on the same REST Client causes side effects because both filters can interfere with each other. For example, OidcClientRequestReactiveFilter can override the token propagated by AccessTokenRequestReactiveFilter, or AccessTokenRequestReactiveFilter can fail if it is called when no token is available to propagate and OidcClientRequestReactiveFilter is expected to get a new token instead.

次に、 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 uses REST Client with an OIDC token propagation Reactive filter to get and propagate an access token to ProtectedResource when either /frontend/user-name-with-oidc-client-token or /frontend/admin-name-with-oidc-client-token is called. Also, FrontendResource uses REST Client with OpenID Connect Token Propagation Reactive Filter to propagate the current incoming access token to ProtectedResource when either /frontend/user-name-with-propagated-token or /frontend/admin-name-with-propagated-token is called.

最後に、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();
	}

}

This exception mapper is only added to verify during the tests that ProtectedResource returns 403 when the token has no expected role. Without this mapper, RESTEasy Reactive would correctly convert the exceptions that escape from REST Client calls to 500 to avoid leaking the information from the downstream resources such as ProtectedResource. However, in the tests, it would not be possible to assert that 500 is caused by an authorization exception instead of some internal error.

アプリケーションの設定

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

# 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

This configuration references Keycloak, which is used by ProtectedResource to verify the incoming access tokens and by OidcClient to get the tokens for a user alice by using a password grant. Both REST clients point to `ProtectedResource’s HTTP address.

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

Keycloak サーバーの起動と設定

アプリケーションを開発モードまたはテストモードで実行するときは、Keycloak サーバーを起動しないでください。 Dev Services for Keycloak がコンテナーを起動します。 詳細は、Running the application in dev mode セクションを参照してください。 必ず、 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

Set {keycloak.version} to 24.0.0 or later.

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

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

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

この quarkus レルムファイルでは、 frontend クライアントと、 alice および admin ユーザーが追加されます。 aliceuser ロールを持ち、 adminuseradmin の両方のロールを持ちます。

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

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

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

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

Open a Dev UI available at /q/dev-ui and click a Provider: Keycloak link in the OpenID Connect Dev UI card.

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

  • Log in as alice, with the password, alice. This user has a user role.

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

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

  • Log out and back in as admin with the password, admin. This user has both admin and user roles.

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

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

In this case, you are testing that FrontendResource can propagate the access tokens from the OpenID Connect Dev UI.

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

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

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

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

そして、実行してみてください:

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

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

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

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

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

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

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

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

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

開発モードでのアプリケーションのテストの詳細は、前述のRunning the application in dev mode セクションを参照してください。

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' \
 )

Now, use this token to call /frontend/user-name-with-propagated-token and /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

In contrast, this command returns 403. Recall that alice only has a user role.

次に 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' \
 )

Use this token to call /frontend/user-name-with-propagated-token:

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

This command returns a 200 status code and the name admin.

Now, use this token to call /frontend/admin-name-with-propagated-token:

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

This command also returns the 200 status code and the name admin because admin has both user and admin roles.

Now, check the FrontendResource methods, which do not propagate the existing tokens but use OidcClient to get and propagate the tokens. As already shown, OidcClient is configured to get the tokens for the alice user, so:

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 ステータスコードを返します。

関連コンテンツ