OpenID Connect(OIDC)とKeycloakを使った認可の中央管理
このガイドでは、Keycloak 認可サービス を使用して、Quarkusアプリケーションが 保護されたリソースへのベアラートークンアクセスを認可する方法を説明します。
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は、保護されたリソースおよび関連する権限が管理される一元化された認可サービスとして機能します。
See the OIDC Bearer token authentication guide for more information about Bearer Token
authentication mechanism. It is important to realize that it is the Bearer Token
authentication mechanism which does the authentication and creates a security identity - while the quarkus-keycloak-authorization
extension is responsible for applying a Keycloak Authorization Policy to this identity based on the current request path and other policy settings.
詳しくは Keycloak認可サービスのドキュメント を参照下さい。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 11+ がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.3
-
動作するコンテナランタイム(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
、 アーカイブ をダウンロードします。
The solution is located in the security-keycloak-authorization-quickstart
directory.
プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
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-keycloak-authorization-quickstart"
このコマンドはプロジェクトを生成し、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'
これにより、ビルドファイルに以下が追加されます:
<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
のエンドポイントを実装することから始めましょう。以下のソースコードからわかるように、これは通常の Jakarta REST リソースです:
package org.acme.security.keycloak.authorization;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.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 jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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 モードで実行する際に Dev Services for Keycloak がコンテナを起動するようになります。詳しくは、以下の Devモードでアプリケーションを実行 セクションを参照してください。
|
デフォルトでは、 quarkus-oidc エクステンションを使用するアプリケーションは、 service タイプのアプリケーションとしてマークされます( quarkus.oidc.application-type 参照)。また、このエクステンションは、 web-app タイプのアプリケーションのみをサポートしますが、認可コードグラント応答の一部として返されたアクセストークンが、ロールのソースとしてマークされている場合に限ります: quarkus.oidc.roles.source=accesstoken ( web-app タイプのアプリケーションは、デフォルトでIDトークンのロールをチェックします)。
|
Keycloakサーバーの起動と設定
アプリケーションをdevモードで実行するときは、Keycloakサーバーを起動しないでください - Dev Services for Keycloak はコンテナを起動します。詳細は、以下の Devモードでアプリケーションを実行 セクションを参照してください。
|
Keycloak Serverを起動するには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 管理 Consoleにアクセスするには、 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.
レルムをインポートすると、リソースのパーミッションが表示されます。

これは、エンドポイントに @RolesAllowed
のアノテーションがない理由を説明しています。リソースのアクセス許可は、Keycloakで直接設定されます。
開発モードでのアプリケーションの実行
アプリケーションを開発モードで実行するには、次を使用します:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
Dev Services for Keycloak は、Keycloakコンテナを起動し、 quarkus-realm.json
をインポートします。
Open a Dev UI available at /q/dev-v1 and click on a Provider: Keycloak
link in an OpenID Connect
Dev UI
card.
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
アプリケーションのテスト
devモードでのアプリケーションのテストについては、上記の 開発モードでアプリケーションを実行 のセクションを参照してください。
curl
でJVMまたはNativeモードで起動したアプリケーションのテストが可能です。
アプリケーションはベアラートークン認可を使用しており、まず最初に行うべきことは、アプリケーションのリソースにアクセスするためにKeycloak Serverからアクセストークンを取得することです。
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' \
)
認可クライアントのインジェクション
場合によっては、 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 Jakarta REST 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
なお、パブリックリソースへの匿名のアクセスを制御する必要がある場合は、デフォルトのテナント設定のみが適用されます。
プログラムによるパーミッションスコープのチェック
In addition to resource permissions, you may want to specify method scopes. The scope usually represents an action that can be performed on a resource. You can create an enforcing Keycloak Authorization Policy with method scope like this:
# path policy with enforced scope 'read' for method 'GET'
quarkus.keycloak.policy-enforcer.paths.1.name=Scope Permission Resource
quarkus.keycloak.policy-enforcer.paths.1.path=/api/protected/standard-way
quarkus.keycloak.policy-enforcer.paths.1.methods.get.method=GET
quarkus.keycloak.policy-enforcer.paths.1.methods.get.scopes=read (1)
# path policies without scope
quarkus.keycloak.policy-enforcer.paths.2.name=Scope Permission Resource
quarkus.keycloak.policy-enforcer.paths.2.path=/api/protected/programmatic-way
quarkus.keycloak.policy-enforcer.paths.3.name=Scope Permission Resource
quarkus.keycloak.policy-enforcer.paths.3.path=/api/protected/annotation-way
1 | ユーザーはリソースパーミッション 'Scope Permission Resource' とスコープ 'read' を持っている必要があります。 |
Request path /api/protected/standard-way
is now secured by the Keycloak Policy Enforcer and does not require any additions (such as @RolesAllowed
annotation). In some cases, you may want to perform the same check programmatically. You are allowed to do that by injecting a SecurityIdentity
instance in your beans, as demonstrated in the example below. Alternatively, if you annotate resource method with the @PermissionsAllowed
annotation, you can achieve the same effect. The following example shows three resource method that all requires same 'read' scope:
import java.security.BasicPermission;
import java.util.List;
import jakarta.inject.Inject;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.keycloak.representations.idm.authorization.Permission;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
@Path("/api/protected")
public class ProtectedResource {
@Inject
SecurityIdentity identity;
@GET
@Path("/standard-way")
public Uni<List<Permission>> standardWay() { (1)
return Uni.createFrom().item(identity.<List<Permission>> getAttribute("permissions"));
}
@GET
@Path("/programmatic-way")
public Uni<List<Permission>> programmaticWay() {
var requiredPermission = new BasicPermission("Scope Permission Resource") {
@Override
public String getActions() {
return "read";
}
};
return identity.checkPermission(requiredPermission).onItem() (2)
.transform(granted -> {
if (granted) {
return identity.getAttribute("permissions");
}
throw new ForbiddenException();
});
}
@PermissionsAllowed("Scope Permission Resource:read") (3)
@GET
@Path("/annotation-way")
public Uni<List<Permission>> annotationWay() {
return Uni.createFrom().item(identity.<List<Permission>> getAttribute("permissions"));
}
}
1 | Request sub-path /standard-way requires both resource permission and scope read according to the configuration properties we set in the application.properties before. |
2 | Request sub-path /programmatic-way only requires permission Scope Permission Resource , but we can enforce scope with SecurityIdentity#checkPermission . |
3 | The @PermissionsAllowed annotation only grants access to the requests with permission Scope Permission Resource and scope read . For more information, see the section Authorization using annotations of the Security Authorization guide. |
マルチテナンシー
マルチテナント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 |
長 |
|
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 |
list of string |
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 |
list of string |
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 |
長 |
|
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 |
|