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)をインストールし、 適切に設定していること
アーキテクチャ
この例では、 FrontendResource
と ProtectedResource
という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 プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
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"
このコマンドは、oidc
、oidc-client-reactive-filter
、oidc-token-propagation-reactive-filter
、resteasy-reactive
エクステンションをインポートして Maven プロジェクトを生成します。
すでに Quarkus プロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトにこれらのエクステンションを追加できます。
quarkus extension add 'oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive'
./mvnw quarkus:add-extension -Dextensions='oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive'
./gradlew addExtension --extensions='oidc,oidc-client-reactive-filter,oidc-token-propagation-reactive,resteasy-reactive'
これにより、 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>
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());
}
}
ご覧のとおり、ProtectedResource
は userName()
と 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
に依存して、着信した、すでに存在するトークンを伝播させることになります。
RestClientWithOidcClientFilter
と RestClientWithTokenPropagationFilter
の両方のインターフェースが同一であることに注意してください。その理由は、同じ REST Client で OidcClientRequestReactiveFilter
と AccessTokenRequestReactiveFilter
を組み合わせると、両方のフィルターが互いに干渉し合って副作用が発生するからです、 例えば、 OidcClientRequestReactiveFilter
は AccessTokenRequestReactiveFilter
によって伝播されたトークンを上書きするかもしれませんし、 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.version
は 17.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
ユーザーが追加されます。alice
は user
ロールを持ち、admin
は user
と admin
の両方のロールを持ちます。
開発モードでのアプリケーションの実行
アプリケーションを開発モードで実行するには、次を使用します。
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
Dev Services for Keycloak は、Keycloak コンテナーを起動し、quarkus-realm.json
をインポートします。
OpenID Connect Dev UI
が提供する Single Page Application
へのログインを求められます。
-
user
のロールを持つalice
(パスワード:alice
) としてログインします-
/frontend/user-name-with-propagated-token
にアクセスすると200
が返されます -
/frontend/admin-name-with-propagated-token
にアクセスすると403
が返されます
-
-
ログアウトし、
admin
とuser
ロールの両方を持つadmin
(パスワード:admin
) としててログインします-
/frontend/user-name-with-propagated-token
にアクセスすると200
が返されます -
/frontend/admin-name-with-propagated-token
にアクセスすると200
が返されます
-
この場合、FrontendResource
が OpenID Connect Dev UI
により取得されたアクセストークンを伝搬できるかどうかをテストしています。
JVM モードでのアプリケーションの実行
「dev
モード」で遊び終わったら、標準のJavaアプリケーションとして実行することができます。
まずコンパイルします。
quarkus build
./mvnw install
./gradlew build
次に、以下を実行してください。
java -jar target/quarkus-app/quarkus-run.jar
ネイティブモードでのアプリケーションの実行
同じデモをネイティブコードにコンパイルすることができます。
これは、生成されたバイナリーにランタイム技術が含まれており、最小限のリソースオーバーヘッドで実行できるように最適化されているため、本番環境にJVMをインストールする必要がないことを意味します。
コンパイルには少し時間がかかるので、このステップはデフォルトで無効になっています。 native
プロファイルを有効にして再度ビルドしてみましょう。
quarkus build --native
./mvnw install -Dnative
./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
メソッドを見てみましょう。OidcClient
は alice
ユーザーのトークンを取得するように設定されています。
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
を返します。