OpenID Connect(OIDC)とKeycloakを使った認可の中央管理
このガイドでは、Quarkusアプリケーションが Keycloak Authorization Services を使用して保護されたリソースへのベアラートークンアクセスを認証する方法を説明します。
The quarkus-keycloak-authorization extension is based on quarkus-oidc and provides a policy enforcer that enforces access to protected resources based on permissions managed by Keycloak and currently can only be used with the Quarkus OIDC service applications.
リソースベースアクセスコントロール(RBAC)に基づく柔軟でダイナミックな認可機能を提供します。
RBAC(Role-Based Access Control)のような特定のアクセス制御メカニズムに基づいて明示的にアクセス強制する代わりに、 quarkus-keycloak-authorization は、認可判断をするKeycloak Authorization Servicesに quarkus-oidc で検証されたベアラアクセストークンを送信してリクエストの名前、識別子、URIに基づいてリソースへのアクセスが許可されているかどうかをチェックします。
Keycloakと連携し、Keycloak Authorization Servicesを有効にして認可の決定を行う場合のみ、 quarkus-keycloak-authorization を使用してください。Keycloakと連携していない場合、またはKeycloakと連携しているが、そのKeycloak Authorization Servicesが認可の決定を行うことを有効にしていない場合は、 quarkus-oidc を使用してください。
アプリケーションから認証を外部化することで、異なるアクセス制御メカニズムを使用してアプリケーションを保護することができ、セキュリティー要件が変更されるたびにアプリケーションを再デプロイする必要がなくなります。Keycloakは、保護されたリソースおよび関連する権限が管理される一元化された認可サービスとして機能します。
Bearer Token 認証メカニズムの詳細については、ガイド OpenID Connectを利用したサービスアプリケーションの保護 を参照してください。認証を行い、セキュリティアイデンティティを作成するのは、 Bearer Token 認証メカニズムであり、一方、 quarkus-keycloak-authorization エクステンションは、現在のリクエストパスやその他のポリシー設定に基づいて、このアイデンティティにKeycloak Authorization Policyを適用する責任があることを認識することが重要です。
詳しくは Keycloak認可サービスのドキュメント を参照下さい。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 11+ がインストールされ、
JAVA_HOMEが適切に設定されていること -
Apache Maven 3.8.6
-
動作するコンテナランタイム(Docker, Podman)
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
アーキテクチャ
この例では、2つのエンドポイントを提供する非常にシンプルなマイクロサービスを構築しています。
-
/api/users/me -
/api/admin
これらのエンドポイントは保護されており、クライアントがリクエストと一緒にベアラートークンを送信している場合にのみアクセスすることができます。
ベアラートークンは、Keycloakサーバーによって発行され、トークンが発行された対象を表します。OAuth 2.0 認可サーバーであるため、トークンはユーザーの代わりに動作するクライアントも参照します。
/api/users/me エンドポイントは、有効なトークンを持つ任意のユーザーがアクセスできます。応答として、ユーザーに関する詳細が記載された JSON ドキュメントを返却します。このエンドポイントは RBAC (Role-Based Access Control) で保護されており、 user のロールで許可されたユーザーのみがこのエンドポイントにアクセスできます。
/api/admin エンドポイントは RBAC (役割ベースのアクセス制御) で保護されており、 admin の役割を付与されたユーザーのみがアクセスできます。
これは、RBACポリシーを使用してリソースへのアクセスを制御する非常にシンプルな例です。しかし、Keycloakは他のタイプのポリシーをサポートしており、より詳細なアクセス制御を行うために使用することができます。この例を使用すると、アプリケーションが認可ポリシーから完全に切り離されており、エンフォースメントは純粋にアクセスされたリソースに基づいていることがわかるでしょう。
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.git 、 アーカイブ をダウンロードします。
ソリューションは security-keycloak-authorization-quickstart directory にあります。
プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
このコマンドはプロジェクトを生成し、 keycloak-authorization エクステンションをインポートします。これはQuarkusアプリケーション用のKeycloakアダプターの実装で、Keycloakサーバーと統合してベアラートークンの認可を実行するのに必要なすべての機能を提供します。
すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで次のコマンドを実行することで、 oidc と keycloak-authorization のエクステンションをプロジェクトに追加できます。
quarkus extension add 'oidc,keycloak-authorization'
./mvnw quarkus:add-extension -Dextensions='oidc,keycloak-authorization'
./gradlew addExtension --extensions='oidc,keycloak-authorization'
これにより、 pom.xml に以下が追加されます:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-authorization</artifactId>
</dependency>
implementation("io.quarkus:quarkus-oidc")
implementation("io.quarkus:quarkus-keycloak-authorization")
まずは /api/users/me エンドポイントを実装してみましょう。下のソースコードを見るとわかるように、これは通常の JAX-RS リソースです。
package org.acme.security.keycloak.authorization;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.jboss.resteasy.annotations.cache.NoCache;
import io.quarkus.security.identity.SecurityIdentity;
@Path("/api/users")
public class UsersResource {
@Inject
SecurityIdentity identity;
@GET
@Path("/me")
@NoCache
public User me() {
return new User(identity);
}
public static class User {
private final String userName;
User(SecurityIdentity identity) {
this.userName = identity.getPrincipal().getName();
}
public String getUserName() {
return userName;
}
}
}
/api/admin エンドポイントのソースコードも非常にシンプルです。
package org.acme.security.keycloak.authorization;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import io.quarkus.security.Authenticated;
@Path("/api/admin")
@Authenticated
public class AdminResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String admin() {
return "granted";
}
}
リソースへのアクセスを明示的に強制するために、 @RolesAllowed のようなアノテーションを定義していないことに注意してください。エクステンションは、Keycloakにある保護されたリソースのURIをマッピングし、それに応じて許可を評価し、Keycloakによって付与される許可に応じてアクセスを許可または拒否する責任を負います。
アプリケーションの設定
OpenID Connect エクステンションを使用すると、 src/main/resources ディレクトリーに配置される application.properties ファイルを使用してアダプター設定を定義することができます。
# OIDC Configuration
%prod.quarkus.oidc.auth-server-url=https://localhost:8543/realms/quarkus
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=secret
quarkus.oidc.tls.verification=none
# Enable Policy Enforcement
quarkus.keycloak.policy-enforcer.enable=true
# Tell Dev Services for Keycloak to import the realm file
# This property is not effective when running the application in JVM or Native modes
quarkus.keycloak.devservices.realm-path=quarkus-realm.json
quarkus.oidc.auth-server-url に %prod. プロファイルのプレフィックスを追加すると、アプリケーションが開発モードで実行されたときに Dev Services for Keycloak がコンテナを起動するようになります。詳しくは、後述の 開発モードでのアプリケーションの実行 をご覧ください。
|
デフォルトでは、 quarkus-oidc エクステンションを使用するアプリケーションは、 service タイプのアプリケーションとしてマークされます( quarkus.oidc.application-type 参照)。また、このエクステンションは、 web-app タイプのアプリケーションのみをサポートしますが、認可コード付与応答の一部として返されたアクセストークンが、ロールのソースとしてマークされている場合に限ります: quarkus.oidc.roles.source=accesstoken ( web-app タイプのアプリケーションは、デフォルトでIDトークンのロールをチェックします)。
|
Keycloak サーバーの起動と設定
アプリケーションを開発モードで実行するときは、Keycloakサーバーを起動しないでください - Dev Services for Keycloak がコンテナを起動します。詳細については、下記の 開発モードでのアプリケーションの実行 セクションを参照してください。
|
Keycloak サーバーを起動するにはDockerを使用し、以下のコマンドを実行するだけです。
docker run --name keycloak -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin -p 8543:8443 -v "$(pwd)"/config/keycloak-keystore.jks:/etc/keycloak-keystore.jks quay.io/keycloak/keycloak:{keycloak.version} start --hostname-strict=false --https-key-store-file=/etc/keycloak-keystore.jks
ここで、 keycloak.version は 17.0.0 以上に設定する必要があります。
Keycloakサーバーには、 localhost:8543/auth でアクセスできるはずです。
Keycloak 管理コンソールにアクセスするには、 admin ユーザーとしてログインしてください。ユーザー名は admin 、パスワードは admin です。
realm設定ファイルをインポートして、新しいrealmを作成します。詳細については、 新しいレルムを作成する 方法についてのKeycloakのドキュメントを参照してください。
レルムをインポートすると、リソースのパーミッションが表示されます。
これは、エンドポイントに @RolesAllowed のアノテーションがない理由を説明しています。リソースのアクセス許可は、Keycloakで直接設定されます。
開発モードでのアプリケーションの実行
アプリケーションを開発モードで実行するには、次を使用します:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
Dev Services for Keycloak は、Keycloak コンテナーを起動し、 quarkus-realm.json をインポートします。
OpenID Connect Dev UI が提供する Single Page Application へのログインを求められます。
-
/api/users/meリソースへのアクセスUser Permissionのみを持つalice(パスワード:alice) としてログインします。-
/api/adminにアクセスすると、403が返されます -
/api/users/meにアクセスすると、200が返されます
-
-
/api/adminリソースにアクセスするためのAdmin Permissionと/api/users/meリソースにアクセスするためのUser Permissionの両方を持っているadmin(パスワード:admin)としてログアウトし、ログインします。-
/api/adminにアクセスすると、200が返されます -
/api/users/meにアクセスすると、200が返されます
-
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-keycloak-authorization-quickstart-runner
アプリケーションのテスト
開発モードでのアプリケーションのテストについては、上記の 開発モードでのアプリケーションの実行 セクションを参照してください。
curl を使用して、JVM またはネイティブモードで起動したアプリケーションをテストできます。
アプリケーションはベアラートークン認可を使用しており、まず最初に行うべきことは、アプリケーションのリソースにアクセスするためにKeycloak サーバーからアクセストークンを取得することです。
export access_token=$(\
curl --insecure -X POST https://localhost:8543/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' \
)
上記の例では、ユーザー alice のアクセストークンを取得しています。
どのユーザーでも http://localhost:8080/api/users/me エンドポイントで、基本的にはユーザーに関する詳細な情報を含む JSON ペイロードを返します。
curl -v -X GET \
http://localhost:8080/api/users/me \
-H "Authorization: Bearer "$access_token
http://localhost:8080/api/admin エンドポイントは、 admin ロールを持つユーザーのみがアクセスできます。以前に発行されたアクセストークンを使用してこのエンドポイントにアクセスしようとすると、サーバーから 403 応答が返ってくるはずです。
curl -v -X GET \
http://localhost:8080/api/admin \
-H "Authorization: Bearer "$access_token
admin エンドポイントにアクセスするには、 admin ユーザーのトークンを取得する必要があります。
export access_token=$(\
curl --insecure -X POST https://localhost:8543/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' \
)
プログラムで権限をチェックする
場合によっては、保護されたリソースにアクセスするためのリクエストが許可されているかどうかを プログラムでチェックしたいこともあるでしょう。Beanに SecurityIdentity インスタンスを注入することで、以下のようにパーミッションをチェックすることができます。
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
@Path("/api/protected")
public class ProtectedResource {
@Inject
SecurityIdentity identity;
@GET
public Uni<List<Permission>> get() {
return identity.checkPermission(new AuthPermission("{resource_name}")).onItem()
.transform(granted -> {
if (granted) {
return identity.getAttribute("permissions");
}
throw new ForbiddenException();
});
}
}
認可クライアントのインジェクション
場合によっては、 Keycloak Authorization Client Java APIを使用して、Keycloakから直接リソースの管理や権限の取得などの特定の操作を行いたいこともあるでしょう。そのためには、以下のように AuthzClient インスタンスをBeanに注入します。
public class ProtectedResource {
@Inject
AuthzClient authzClient;
}
Note: AuthzClient を直接使用する場合は、必ず quarkus.keycloak.policy-enforcer.enable=true を設定してください。そうしないと、インジェクションに使用できる Bean がありません。
保護リソースのマッピング
デフォルトでは、エクステンションは Keycloak からオンデマンドでリソースを取得します。 URI は、保護されるべきアプリケーション内のリソースをマップするために使用されます。
この動作を無効にして、起動時にリソースをフェッチしたい場合は、以下の設定を使用します。
quarkus.keycloak.policy-enforcer.lazy-load-paths=false
Keycloakにあるリソースの数によっては、それらを取得するのにかかる時間がアプリケーションの起動時間に影響することに注意してください。
保護されたリソースの設定についての詳細
デフォルトの構成では、Keycloakはロールを管理し、誰がどのルートにアクセスできるかを決定します。
To configure the protected routes using the @RolesAllowed annotation or the application.properties file, check the Using OpenID Connect Adapter to Protect JAX-RS Applications and Security Authorization guides. For more details, check the Security guide.
パブリックリソースへのアクセス
quarkus-keycloak-authorization のポリシーを適用せずにパブリックリソースにアクセスしたい場合は、 Security 認可 ガイドで説明されているように、 application.properties で permit HTTP Policy の設定を作成する必要があります。
以下のようなKeycloak Authorization Policyを使ったポリシーチェックを無効にします。
quarkus.keycloak.policy-enforcer.paths.1.path=/api/public
quarkus.keycloak.policy-enforcer.paths.1.enforcement-mode=DISABLED
これはもう必要ありません。
匿名ユーザのパブリックリソースへのアクセスをブロックしたい場合は、強制力のあるKeycloak Authorization Policyを作成します。
quarkus.keycloak.policy-enforcer.paths.1.path=/api/public-enforcing
quarkus.keycloak.policy-enforcer.paths.1.enforcement-mode=ENFORCING
なお、パブリックリソースへの匿名のアクセスを制御する必要がある場合は、デフォルトのテナント設定のみが適用されます。
マルチテナンシー
マルチテナントOpenID Connectサービスアプリケーション の場合と同様に、各テナントごとに複数のポリシーエンフォースメント構成を設定することができます。
例えば、以下のようになります。
quarkus.keycloak.policy-enforcer.enable=true
# Default Tenant
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE
quarkus.keycloak.policy-enforcer.paths.1.name=Permission Resource
quarkus.keycloak.policy-enforcer.paths.1.path=/api/permission
quarkus.keycloak.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim
# Service Tenant
quarkus.oidc.service-tenant.auth-server-url=${keycloak.url}/realms/quarkus
quarkus.oidc.service-tenant.client-id=quarkus-app
quarkus.oidc.service-tenant.credentials.secret=secret
quarkus.keycloak.service-tenant.policy-enforcer.enforcement-mode=PERMISSIVE
quarkus.keycloak.service-tenant.policy-enforcer.paths.1.name=Permission Resource Service
quarkus.keycloak.service-tenant.policy-enforcer.paths.1.path=/api/permission
quarkus.keycloak.service-tenant.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim
# WebApp Tenant
quarkus.oidc.webapp-tenant.auth-server-url=${keycloak.url}/realms/quarkus
quarkus.oidc.webapp-tenant.client-id=quarkus-app
quarkus.oidc.webapp-tenant.credentials.secret=secret
quarkus.oidc.webapp-tenant.application-type=web-app
quarkus.oidc.webapp-tenant.roles.source=accesstoken
quarkus.keycloak.webapp-tenant.policy-enforcer.enforcement-mode=PERMISSIVE
quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.name=Permission Resource WebApp
quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.path=/api/permission
quarkus.keycloak.webapp-tenant.policy-enforcer.paths.1.claim-information-point.claims.static-claim=static-claim
設定リファレンス
この設定は、 Keycloak Policy Enforcer の公式設定に基づいています。異なる設定オプションについての詳細をお探しの方は、こちらのドキュメントをご覧ください。
ビルド時に固定される設定プロパティ - それ以外の設定プロパティは実行時に上書き可能
タイプ |
デフォルト |
|
|---|---|---|
Adapters will make separate HTTP invocations to the Keycloak server to turn an access code into an access token. This config option defines how many connections to the Keycloak server should be pooled Environment variable: Show more |
int |
|
Specifies how policies are enforced. Environment variable: Show more |
|
|
Defines the limit of entries that should be kept in the cache Environment variable: Show more |
int |
|
Defines the time in milliseconds when the entry should be expired Environment variable: Show more |
long |
|
Specifies how the adapter should fetch the server for resources associated with paths in your application. If true, the policy enforcer is going to fetch resources on-demand accordingly with the path being requested Environment variable: Show more |
boolean |
|
Specifies how scopes should be mapped to HTTP methods. If set to true, the policy enforcer will use the HTTP method from the current request to check whether access should be granted Environment variable: Show more |
boolean |
|
The name of a resource on the server that is to be associated with a given path Environment variable: Show more |
string |
|
A URI relative to the application’s context path that should be protected by the policy enforcer Environment variable: Show more |
string |
|
The name of the HTTP method Environment variable: Show more |
string |
required |
An array of strings with the scopes associated with the method Environment variable: Show more |
文字列のリスト |
required |
A string referencing the enforcement mode for the scopes associated with a method Environment variable: Show more |
|
|
Specifies how policies are enforced Environment variable: Show more |
|
|
Environment variable: |
|
|
Environment variable: |
|
|
Environment variable: |
|
|
Environment variable: |
|
|
タイプ |
デフォルト |
|
Adapters will make separate HTTP invocations to the Keycloak server to turn an access code into an access token. This config option defines how many connections to the Keycloak server should be pooled Environment variable: Show more |
int |
|
Specifies how policies are enforced. Environment variable: Show more |
|
|
The name of a resource on the server that is to be associated with a given path Environment variable: Show more |
string |
|
A URI relative to the application’s context path that should be protected by the policy enforcer Environment variable: Show more |
string |
|
The name of the HTTP method Environment variable: Show more |
string |
required |
An array of strings with the scopes associated with the method Environment variable: Show more |
文字列のリスト |
required |
A string referencing the enforcement mode for the scopes associated with a method Environment variable: Show more |
|
|
Specifies how policies are enforced Environment variable: Show more |
|
|
Environment variable: |
|
|
Environment variable: |
|
|
Defines the limit of entries that should be kept in the cache Environment variable: Show more |
int |
|
Defines the time in milliseconds when the entry should be expired Environment variable: Show more |
long |
|
Specifies how the adapter should fetch the server for resources associated with paths in your application. If true, the policy enforcer is going to fetch resources on-demand accordingly with the path being requested Environment variable: Show more |
boolean |
|
Environment variable: |
|
|
Environment variable: |
|
|
Specifies how scopes should be mapped to HTTP methods. If set to true, the policy enforcer will use the HTTP method from the current request to check whether access should be granted Environment variable: Show more |
boolean |
|