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

OpenID Connect Client と Token Propagation のクイックスタート

このクイックスタートでは、OpenID Connect Client Reactive Filter を使用して、アクセストークンを HTTP Authorization Bearer アクセストークンとして取得および伝播する方法と、受信した HTTP Authorization Bearer アクセストークンを伝播する OpenID Token Propagation Reactive Filter を使用する方法を示します。

Quarkus の Oidc Client および Token Propagation のサポートに関する詳細は、OpenID Connect Client と Token Propagation のリファレンスガイド を参照してください。

ベアラートークン認証を使用してアプリケーションを保護する必要がある場合は、 OIDCベアラートークン 認証ガイドもお読みください。

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.9.3

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

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

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

  • jq tool

アーキテクチャ

この例では、 FrontendResourceProtectedResource という2つの Jakarta REST リソースからなるアプリケーションを構築します。 FrontendResource はアクセストークンを ProtectedResource に伝播し、伝播前にまずトークンを取得するために OpenID Connect Client Reactive Filter を使用するか、受信した既に存在するアクセストークンを伝播するために OpenID Token Propagation Reactive Filter のいずれかを使用しています。

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

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

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

  • /frontend/user-name-with-propagated-token

  • /frontend/admin-name-with-propagated-token

FrontendResource は、/frontend/user-name-with-oidc-client または /frontend/admin-name-with-oidc-client が呼び出された場合、REST Client と OpenID Connect Client Reactive Filter を使用して、ProtectedResource へのアクセストークンを取得および伝播します。また、/frontend/user-name-with-propagated-token もしくは /frontend/admin-name-with-propagated-token が呼び出されると、REST Client と OpenID Connect Token Propagation Reactive Filter を使用して、ProtectedResource への現在の受信アクセストークンを伝播します。

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

  • /protected/user-name

  • /protected/admin-name

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

ソリューション

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

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

The solution is located in the security-openid-connect-client-quickstart directory.

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 オプションを追加します。

For more information about how to install and use the Quarkus CLI, see the Quarkus CLI guide.

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.4.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ユーザーの場合:

  • If using cmd, (don’t use backward slash \ and put everything on the same line)

  • If using Powershell, wrap -D parameters in double quotes e.g. "-DprojectArtifactId=security-openid-connect-client-quickstart"

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

すでに 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'

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

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 から抽出されたものです。

次に、 OidcClientRequestReactiveFilter のRESTクライアントと、 AccessTokenRequestReactiveFilter のRESTクライアントを追加しましょう。 FrontendResource は、この2つのクライアントを使用して、 ProtectedResource を呼び出します:

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

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

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

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

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

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

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

RestClientWithOidcClientFilter では、 OidcClientRequestReactiveFilter に依存して、トークンを獲得し、伝播させ、

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

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

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

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

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

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

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

RestClientWithTokenPropagationFilter では AccessTokenRequestReactiveFilter に依存して、着信した、すでに存在するトークンを伝播させることになります。

RestClientWithOidcClientFilterRestClientWithTokenPropagationFilter の両方のインターフェースが同一であることに注意してください。その理由は、同じ REST Client で OidcClientRequestReactiveFilterAccessTokenRequestReactiveFilter を組み合わせると、両方のフィルターが互いに干渉し合って副作用が発生するからです、 例えば、 OidcClientRequestReactiveFilterAccessTokenRequestReactiveFilter によって伝播されたトークンを上書きするかもしれませんし、 AccessTokenRequestReactiveFilter は伝播できるトークンがないときに呼ばれると失敗するかもしれませんが、代わりに OidcClientRequestReactiveFilter には新しいトークンを取得することが期待されます。

それでは、FrontendResource を追加して、アプリケーションの作成を完了しましょう。

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

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

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

import io.smallrye.mutiny.Uni;

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

    @Inject
    @RestClient
    RestClientWithTokenPropagationFilter restClientWithTokenPropagationFilter;

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

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

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

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

FrontendResource は、/frontend/user-name-with-oidc-client または /frontend/admin-name-with-oidc-client が呼び出された場合、REST Client と OpenID Connect Client Reactive Filter を使用して、ProtectedResource へのアクセストークンを取得および伝播します。また、/frontend/user-name-with-propagated-token もしくは /frontend/admin-name-with-propagated-token が呼び出されると、REST Client と OpenID Connect Token Propagation Reactive Filter を使用して、ProtectedResource への現在の受信アクセストークンを伝播します。

最後に、Jakarta REST ExceptionMapper を追加しましょう:

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

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

import org.jboss.resteasy.reactive.ClientWebApplicationException;

@Provider
public class FrontendExceptionMapper implements ExceptionMapper<ClientWebApplicationException> {

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

}

この例外マッパーは、トークンに期待されるロールがない場合に ProtectedResource が 403 を返すことをテスト中に検証するためにのみ追加されます。このマッパーがないと、RESTEasy Reactive は、REST クライアント呼び出しからエスケープされる例外を 500 に正しく変換して、ProtectedResource などのダウンストリームリソースからの情報漏えいを回避しますが、テストでは 500 が実際は内部エラーではなく、認可例外が原因であることをアサートすることはできません。

アプリケーションの設定

コードの準備ができたので、次はアプリケーションを設定します。

# 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 not effective when running the application in JVM or Native modes but only in dev and test modes.

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

# Configure OIDC Client

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

# Configure REST Clients

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

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

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

quarkus.oidc.auth-server-url%prod. プロファイルプレフィックスを追加することで、アプリケーションを dev または test モードで実行する際に、 Dev Services for Keycloak がコンテナを起動するようになります。詳しくは、以下の Devモードでアプリケーションを実行 セクションを参照してください。

Keycloak サーバーの起動と設定

Do not start the Keycloak server when you run the application in dev mode or test modes - Dev Services for Keycloak will launch a container. See Running the Application in Dev mode section below for more information. Make sure to put the realm configuration file on the classpath (target/classes directory) so that it gets imported automatically when running in dev mode - unless you have already built a complete solution in which case this realm file will be added to the classpath during the build.

Keycloak サーバーを起動するにはDockerを使用し、以下のコマンドを実行するだけです。

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

ここで、keycloak.version17.0.0 以上に設定する必要があります。

localhost:8180 で Keycloak サーバーにアクセスできるはずです。

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

Import the realm configuration file to create a new realm. For more details, see the Keycloak documentation about how to create a new realm.

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

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

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

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

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

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

OpenID Connect Dev UI が提供する Single Page Application へのログインを求められます。

  • user のロールを持つ alice (パスワード: alice) としてログインします

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

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

  • ログアウトし、adminuser ロールの両方を持つ admin (パスワード: admin) としててログインします

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

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

この場合、FrontendResourceOpenID Connect Dev UI により取得されたアクセストークンを伝搬できるかどうかをテストしています。

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

dev モード」で遊び終わったら、標準の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

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

devモードでのアプリケーションのテストについては、上記の Devモードでのアプリケーションの実行 セクションを参照してください。

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

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

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

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

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

ステータスコード 200 と名前 alice を返します。

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

403 を返します - alice には user ロールしかないことを思い出してください。

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

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

このトークンを使用して、/frontend/user-name-with-propagated-token/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

admin には user ロールと admin ロールの両方があるため、200 ステータスコードと名前 admin を返します。

ここで、既存のトークンは伝播しないが、OidcClient を使用してトークンを取得および伝播する FrontendResource メソッドを見てみましょう。OidcClientalice ユーザーのトークンを取得するように設定されています。

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

ステータスコード 200 と名前 alice を返します

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

ステータスコード 403 を返します。