これは、なにをしたくて書いたもの?
以前、WildFly 34(Jakarta EE 10)とKeycloakでOpenID Connectを使った認証、認可を試しました。
WildFly 34とKeycloak 26でJakarta SecurityのOpenID Connectを使った認証と認可設定を試す - CLOVER🍀
今回は、こちらをWildFly 41(Jakarta EE 11)でやってみたいと思います。バージョンアップ後の確認ですね。
結果としてほぼ変更せずに動作したので、簡単に済ませていきたいと思います。
Jakarta Security 4.0
Jakarta EE 11におけるJakarta Securityのバージョンは4.0です。
Jakarta Security 4.0 | Jakarta EE | The Eclipse Foundation
仕様書を見てもJakarta Security 3.0(Jakarta EE 10)からの変更点が書かれていなかったのですが、仕様のページに
まとめられていました。
変更はこのあたりのようです。
- Basic API (handler) for Multiple authentication mechanisms … 複数の認証メカニズムのための基本API(ハンドラー)
- Qualifiers for build-in authentication mechanisms … 組み込み認証メカニズムのQualifier
- In-memory identity store … インメモリーIDストアー
削除や互換性のない変更はこちら。
- Remove references to the SecurityManager … SecurityManagerへの参照の削除
- Build-in authentication mechanisms now have a qualifier by default, whereas before they were unqualified … 組み込み認証メカニズムにはQualifierがなかったので、デフォルトで付与するように変更
Jakarta Security 4.0 | Jakarta EE | The Eclipse Foundation
というわけで、そう大きくは変わっていなさそうですね。
Jakarta Securityの用語は、
- Authentication Mechanism(認証メカニズム)
- 認証を実行するメカニズムで、呼び出し元と対話して資格情報を取得し、アイデンティティーストアを呼び出して指定された資格情報をユーザーのアイデンティティーと照合する
- 一致した場合、認証メカニズムは見つかったアイデンティティーを使用して属性(プリンシパル)を設定し、認証されたサブジェクトを構築する
- 一致しなかった場合はログイン失敗となる
- Caller(呼び出し元)、Caller Principal
- アプリケーションにリクエストを送信したり、アプリケーションAPIを呼び出したりするユーザー
- Caller Principal(呼び出し元プリンシパル)とは、そのユーザーを表すプリンシパルオブジェクト
- HAM
- Jakarta Securityで定義されているインターフェースである、HttpAuthenticationMechanismの略称
- Identity Store(アイデンティティーストア)
- ユーザー、グループ、ロール、権限などのアプリケーション固有のセキュリティデータにアクセスできるコンポーネント
- 通常、RDBMS、LDAP、ファイルシステム、その他類似のリソースなどのデータソースと1対1で関連付けられている
- 同義語: セキュリティプロバイダー、リポジトリー、ストア、ログインモジュール(JAAS)、アイデンティティーマネージャー、サービスプロバイダー、Relying Party、Authenticator、ユーザーサービス
- SAM
- Jakarta Authenticationで定義されているインターフェースであるServerAuthModuleの略称
Jakarta Securityによる認証メカニズムは、Jakarta Servlet上で動作することを想定しています。
このあたり、詳しくは以前のエントリーを参照してください。
WildFly 34とKeycloak 26でJakarta SecurityのOpenID Connectを使った認証と認可設定を試す - CLOVER🍀
Jakarta Securityがサポートしている認証方法は、以下の4つです。
- Basic認証
- Form認証
- カスタムForm認証
- OpenID Connect認証
WildFly 41が使用しているJakarta Securityの実装は、Soteriaです。バージョンは4.0.2ですね。
GitHub - eclipse-ee4j/soteria: Soteria, a Jakarta Security implementation · GitHub
では、簡単に試してみましょう。
お題
お題は、前回のエントリーと同じです。以下の構成で、Jakarta SecurityのOpenID Connectを使った認証を試してみたいと
思います。
flowchart LR
A[Browser] --> |HTTP| B[WildFly]
A --> |Redirect / HTTP| C[Keycloak]
B --> |Get Token / Verify Token| C
OpenID Connectの用語の合わせると、こうなりますね。
flowchart LR
A[Browser] --> |HTTP| B[Relying Party/WildFly]
A --> |Redirect / HTTP| C[OpenID Provider/Keycloak]
B --> |Get Token / Verify Token| C
アクセスするユーザーとしては、リーダーと一般ユーザーの2種類(のロール)を用意することにします。
Keycloadk上のリソースはTerraformで作成するものとします。
環境
今回の環境はこちら。
$ java --version openjdk 25.0.3 2026-04-21 OpenJDK Runtime Environment (build 25.0.3+9-2-24.04.2-Ubuntu) OpenJDK 64-Bit Server VM (build 25.0.3+9-2-24.04.2-Ubuntu, mixed mode, sharing) $ mvn --version Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 25.0.3, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-137-generic", arch: "amd64", family: "unix"
Keycloak。
$ bin/kc.sh --version Keycloak 26.7.1 JVM: 25.0.3 (Eclipse Adoptium OpenJDK 64-Bit Server VM 25.0.3+9-LTS) OS: Linux 6.8.0-137-generic amd64
Keycloakは172.17.0.2で動作しているものとし、以下のコマンドで起動させておきます。
bin/kc.sh start-dev
Keycloakの設定はこちら。
grep -vE '^#|^$' conf/keycloak.conf bootstrap-admin-username=keycloak-admin bootstrap-admin-password=password http-host=0.0.0.0 http-access-log-enabled=true
Terraform。
$ terraform version Terraform v1.15.8 on linux_amd64
TerraformでKeycloakのリソースを作成する
まずはTerraformでKeycloak上のリソースを定義します。
versions.tf
terraform { required_version = "1.15.8" required_providers { keycloak = { source = "keycloak/keycloak" version = "5.9.0" } } }
あとはクライアント、クライアントプロトコルマッパー、ユーザー、ロール、ロールのユーザーへのアサインを定義。
main.tf
provider "keycloak" { client_id = "admin-cli" username = "keycloak-admin" password = "password" url = "http://172.17.0.2:8080" } ## Realm resource "keycloak_realm" "sample_realm" { realm = "sample-realm" display_name = " Realm" enabled = true } ## Client resource "keycloak_openid_client" "ee_client" { realm_id = keycloak_realm.sample_realm.id client_id = "ee-client" name = "Jakarta Security Client" enabled = true access_type = "CONFIDENTIAL" standard_flow_enabled = true direct_access_grants_enabled = true # テスト用 # pkce_code_challenge_method = "S256" # PKCE root_url = "http://localhost:8080" valid_redirect_uris = ["http://localhost:8080/*"] } ## Client Protocol Mapper data "keycloak_openid_client_scope" "roles" { # client scopeのmapperとして使う場合 realm_id = keycloak_realm.sample_realm.id name = "roles" } resource "keycloak_openid_user_realm_role_protocol_mapper" "role_mapper" { realm_id = keycloak_realm.sample_realm.id name = "roles as top" client_id = keycloak_openid_client.ee_client.id # dedicated mapperとして使う場合 # client_scope_id = data.keycloak_openid_client_scope.roles.id # client scopeのmapperとして使う場合 claim_name = "roles" claim_value_type = "String" multivalued = true add_to_access_token = true add_to_id_token = false } ## User resource "keycloak_user" "leader_user" { realm_id = keycloak_realm.sample_realm.id username = "leader-user" email = "leader-user@example.com" first_name = "Leader" last_name = "User" enabled = true initial_password { value = "password" temporary = false } } resource "keycloak_user" "general_user" { realm_id = keycloak_realm.sample_realm.id username = "general-user" email = "general-user@example.com" first_name = "General" last_name = "User" enabled = true initial_password { value = "password" temporary = false } } ## Role resource "keycloak_role" "leader_role" { realm_id = keycloak_realm.sample_realm.id name = "leader-role" } resource "keycloak_role" "general_role" { realm_id = keycloak_realm.sample_realm.id name = "general-role" } ## Assign Role resource "keycloak_user_roles" "leader_user_assign_leader_role" { realm_id = keycloak_realm.sample_realm.id user_id = keycloak_user.leader_user.id role_ids = [ keycloak_role.leader_role.id ] } resource "keycloak_user_roles" "general_user_assign_general_role" { realm_id = keycloak_realm.sample_realm.id user_id = keycloak_user.general_user.id role_ids = [ keycloak_role.general_role.id ] } output "ee_client_client_secret" { value = keycloak_openid_client.ee_client.client_secret sensitive = true }
Jakarta Securityで認証した後にユーザーに割り当てたロールを認識させるために、クライアントプロトコルマッパーを
定義しています。
## Client Protocol Mapper data "keycloak_openid_client_scope" "roles" { # client scopeのmapperとして使う場合 realm_id = keycloak_realm.sample_realm.id name = "roles" } resource "keycloak_openid_user_realm_role_protocol_mapper" "role_mapper" { realm_id = keycloak_realm.sample_realm.id name = "roles as top" client_id = keycloak_openid_client.ee_client.id # dedicated mapperとして使う場合 # client_scope_id = data.keycloak_openid_client_scope.roles.id # client scopeのmapperとして使う場合 claim_name = "roles" claim_value_type = "String" multivalued = true add_to_access_token = true add_to_id_token = false }
またPKCEも必須にしてみたのですが、Jakarta Securityが対応していなかったので使うのを諦めました。
## Client resource "keycloak_openid_client" "ee_client" { realm_id = keycloak_realm.sample_realm.id client_id = "ee-client" name = "Jakarta Security Client" enabled = true access_type = "CONFIDENTIAL" standard_flow_enabled = true direct_access_grants_enabled = true # テスト用 # pkce_code_challenge_method = "S256" # PKCE root_url = "http://localhost:8080" valid_redirect_uris = ["http://localhost:8080/*"] }
リソースを作成。
$ terraform init $ terraform apply
作成した後は、クライアントシークレットを取得しておきます。
$ terraform output ee_client_client_secret "g1brJ8rSsoHXb6V3oO0RCqstk7mgXv8uQqKf3hiSBHlzPNSQdBx8igOxY6QWOMcu7Ev5OBKojQZJtFfrdThzC3"
ソースコードを作成する
それでは、ソースコードを作成していきます。
Maven依存関係など。
<properties> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.wildfly.bom</groupId> <artifactId>wildfly-ee-with-tools</artifactId> <version>41.0.0.Final</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.wildfly.bom</groupId> <artifactId>wildfly-expansion-with-tools</artifactId> <version>41.0.0.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.enterprise</groupId> <artifactId>jakarta.enterprise.cdi-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.inject</groupId> <artifactId>jakarta.inject-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.security.enterprise</groupId> <artifactId>jakarta.security.enterprise-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.microprofile.config</groupId> <artifactId>microprofile-config-api</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>ROOT</finalName> <plugins> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>6.0.1.Final</version> <executions> <execution> <id>package</id> <goals> <goal>package</goal> </goals> </execution> </executions> <configuration> <overwrite-provisioned-server>true</overwrite-provisioned-server> <discover-provisioning-info> <version>41.0.0.Final</version> <suggest>true</suggest> </discover-provisioning-info> <commands> <command>/subsystem=undertow/application-security-domain=other:write-attribute(name=integrated-jaspi, value=false)</command> </commands> </configuration> </plugin> </plugins> </build>
Jakarta Securityを使った、OpenID Connectの設定。
src/main/java/org/littlewings/security/oidc/OidcConfig.java
package org.littlewings.security.oidc; import jakarta.annotation.security.DeclareRoles; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.security.enterprise.authentication.mechanism.http.OpenIdAuthenticationMechanismDefinition; import jakarta.security.enterprise.authentication.mechanism.http.openid.ClaimsDefinition; import jakarta.security.enterprise.authentication.mechanism.http.openid.OpenIdConstant; import org.eclipse.microprofile.config.inject.ConfigProperty; @OpenIdAuthenticationMechanismDefinition( providerURI = "${oidcConfig.providerUri}", clientId = "${oidcConfig.clientId}", clientSecret = "${oidcConfig.clientSecret}", scope = {OpenIdConstant.OPENID_SCOPE, OpenIdConstant.EMAIL_SCOPE, OpenIdConstant.PROFILE_SCOPE}, // default redirectURI = "${baseURL}/callback", // redirectToOriginalResource = true, // trueにすると、アクセスしたページに戻ろうとする tokenAutoRefresh = true, claimsDefinition = @ClaimsDefinition(callerGroupsClaim = "roles")) @DeclareRoles({"leader-role", "general-role"}) @ApplicationScoped @Named("oidcConfig") // ELで参照するのに必要 public class OidcConfig { @Inject @ConfigProperty(name = "security.oidc.provider-uri") private String providerUri; @Inject @ConfigProperty(name = "security.oidc.client-id") private String clientId; @Inject @ConfigProperty(name = "security.oidc.client-secret") private String clientSecret; // getter/setterは省略 }
@OpenIdAuthenticationMechanismDefinitionアノテーションの各プロパティーは、設定で変更できるようにEL式にしています。
値の取得元は、このBean自身ですが。
定義はMicroProfile Configで行っています。
src/main/resources/META-INF/microprofile-config.properties
security.oidc.provider-uri=http://172.17.0.2:8080/realms/sample-realm security.oidc.client-id=ee-client security.oidc.client-secret=g1brJ8rSsoHXb6V3oO0RCqstk7mgXv8uQqKf3hiSBHlzPNSQdBx8igOxY6QWOMcu7Ev5OBKojQZJtFfrdThzC3
ロールの宣言もここに入れています。
@DeclareRoles({"leader-role", "general-role"})
アクセス制御を行うServletを書いていきましょう。前回同様、今回も認証・認可における動作確認をServletで行って
いきます。
まずはベースから。
src/main/java/org/littlewings/security/oidc/OidcAccessTestServlet.java
package org.littlewings.security.oidc; import jakarta.inject.Inject; import jakarta.security.enterprise.SecurityContext; import jakarta.security.enterprise.identitystore.openid.AccessToken; import jakarta.security.enterprise.identitystore.openid.IdentityToken; import jakarta.security.enterprise.identitystore.openid.OpenIdContext; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.security.Principal; import java.util.Map; public abstract class OidcAccessTestServlet extends HttpServlet { @Inject private SecurityContext securityContext; @Inject private OpenIdContext openIdContext; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter writer = response.getWriter(); Principal principal = securityContext.getCallerPrincipal(); writer.printf("=============== Principal ===============%n"); writer.printf("name = %s%n", principal.getName()); writer.printf("=============== Principal ===============%n"); writer.println(); if (!"anonymous".equals(principal.getName())) { writer.printf("=============== OpenIdContext ===============%n"); writer.printf("subject = %s%n", openIdContext.getSubject()); writer.println(); writer.printf( "claims preferred_username = %s%n", openIdContext.getClaims().getPreferredUsername().get()); writer.printf("claims roles = %s%n", openIdContext.getClaims().getArrayStringClaim("roles")); writer.println(); AccessToken accessToken = openIdContext.getAccessToken(); writer.printf("access token string = %s%n", accessToken.getToken()); writer.println(); writer.printf("access tokens claims%n"); for (Map.Entry<String, Object> claim : accessToken.getClaims().entrySet()) { writer.printf(" %s = %s%n", claim.getKey(), claim.getValue()); } writer.println(); IdentityToken idToken = openIdContext.getIdentityToken(); writer.printf("id token string = %s%n", idToken.getToken()); writer.println(); writer.printf("id tokens claims%n"); for (Map.Entry<String, Object> claim : idToken.getClaims().entrySet()) { writer.printf(" %s = %s%n", claim.getKey(), claim.getValue()); } writer.printf("=============== OpenIdContext ===============%n"); } } }
こちらを継承して、アクセス制御を行うServletを書いていきます。
リーダーと一般ユーザーの両方がアクセスできるServletです。
sc/main/java/org/littlewings/security/oidc/LeaderOrGeneralAccessAccessTestServlet.java
package org.littlewings.security.oidc; import jakarta.enterprise.context.ApplicationScoped; import jakarta.servlet.annotation.HttpConstraint; import jakarta.servlet.annotation.ServletSecurity; import jakarta.servlet.annotation.WebServlet; @WebServlet("/leader-or-general") @ServletSecurity(@HttpConstraint(rolesAllowed = {"leader-role", "general-role"})) @ApplicationScoped public class LeaderOrGeneralAccessAccessTestServlet extends OidcAccessTestServlet {}
リーダーだけがアクセスできるServlet。
src/main/java/org/littlewings/security/oidc/LeaderOnlyAccessAccessTestServlet.java
package org.littlewings.security.oidc; import jakarta.enterprise.context.ApplicationScoped; import jakarta.servlet.annotation.HttpConstraint; import jakarta.servlet.annotation.ServletSecurity; import jakarta.servlet.annotation.WebServlet; @WebServlet("/leader-only") @ServletSecurity(@HttpConstraint(rolesAllowed = "leader-role")) @ApplicationScoped public class LeaderOnlyAccessAccessTestServlet extends OidcAccessTestServlet {}
一般ユーザーだけがアクセスできるServlet。
src/main/java/org/littlewings/security/oidc/GeneralOnlyAccessAccessTestServlet.java
package org.littlewings.security.oidc; import jakarta.enterprise.context.ApplicationScoped; import jakarta.servlet.annotation.HttpConstraint; import jakarta.servlet.annotation.ServletSecurity; import jakarta.servlet.annotation.WebServlet; @WebServlet("/general-only") @ServletSecurity(@HttpConstraint(rolesAllowed = "general-role")) @ApplicationScoped public class GeneralOnlyAccessAccessTestServlet extends OidcAccessTestServlet {}
どのロールでもアクセスできないServlet。
src/main/java/org/littlewings/security/oidc/AccessDenyAccessAccessTestServlet.java
package org.littlewings.security.oidc; import jakarta.enterprise.context.ApplicationScoped; import jakarta.servlet.annotation.HttpConstraint; import jakarta.servlet.annotation.ServletSecurity; import jakarta.servlet.annotation.WebServlet; @WebServlet("/deny") @ServletSecurity(@HttpConstraint(ServletSecurity.EmptyRoleSemantic.DENY)) @ApplicationScoped public class AccessDenyAccessAccessTestServlet extends OidcAccessTestServlet {}
アクセス制限をつけないServlet。
package org.littlewings.security.oidc;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.servlet.annotation.WebServlet;
@WebServlet("/plain")
@ApplicationScoped
public class PlainServlet extends OidcAccessTestServlet {}
ログイン時のコールバック用のServlet。
src/main/java/org/littlewings/security/oidc/CallbackServlet.java
package org.littlewings.security.oidc; import jakarta.inject.Inject; import jakarta.security.enterprise.SecurityContext; import jakarta.security.enterprise.authentication.mechanism.http.openid.OpenIdConstant; import jakarta.security.enterprise.identitystore.openid.OpenIdContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.security.Principal; import java.util.Optional; @WebServlet("/callback") public class CallbackServlet extends HttpServlet { @Inject private SecurityContext securityContext; @Inject private OpenIdContext context; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Principal principal = securityContext.getCallerPrincipal(); if (!"anonymous".equals(principal.getName())) { Optional<String> originalRequest = context.getStoredValue(request, response, OpenIdConstant.ORIGINAL_REQUEST); String originalRequestString = originalRequest.get(); response.sendRedirect(originalRequestString); } } }
redirectURIに割り当てていたのはこちらですね。
@OpenIdAuthenticationMechanismDefinition( providerURI = "${oidcConfig.providerUri}", clientId = "${oidcConfig.clientId}", clientSecret = "${oidcConfig.clientSecret}", scope = {OpenIdConstant.OPENID_SCOPE, OpenIdConstant.EMAIL_SCOPE, OpenIdConstant.PROFILE_SCOPE}, redirectURI = "${baseURL}/callback", // redirectToOriginalResource = true, // trueにすると、アクセスしたページに戻ろうとする tokenAutoRefresh = true, claimsDefinition = @ClaimsDefinition(callerGroupsClaim = "roles") )
このServletではOpenIdConstantのORIGINAL_REQUESTを使ってOpenIdContextから値を取得することで、
未ログイン時にアクセスしたURLにリダイレクトさせています。
近いことはredirectToOriginalResourceをtrueにすることでもできるのですが、こちらはOpenID Connectのフローで使われる
大量のQueryStringも付いてきます…。
これで準備は完了です。
動作確認する
それでは動作確認していきましょう。やることは前のエントリーと同じです。
WildFlyを起動。
ちなみに、今回のWildFly Maven Pluginの設定だとパッケージングして起動する方法ではJASPIが無効にならずうまくいきません。
※JASPIは後で
まずは未ログイン状態でhttp://localhost:8080/plainにアクセスしてみます。
結果。
=============== Principal =============== name = anonymous =============== Principal ===============
次にhttp://localhost:8080/leader-or-generalにアクセスすると、Keycloakのログイン画面にリダイレクトされます。
まずはリーダーとしてログイン。

ログインすると、コールバック用のSerlvetに1度戻ってきてから、見ていたページにリダイレクトします。

テキストでも。
=============== Principal ===============
name = leader-user
=============== Principal ===============
=============== OpenIdContext ===============
subject = 02139241-52ef-409c-8388-454536d4a5cf
claims preferred_username = leader-user
claims roles = [leader-role]
access token string = eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJWSDkxSlJURXR5bDZUN1Bkb2Zib0l6STBGREJ1LVlZYl9GU1dObFlzNWhzIn0.eyJleHAiOjE3ODY4NzEyODQsImlhdCI6MTc4Njg3MDk4NCwiYXV0aF90aW1lIjoxNzg2ODcwOTg0LCJqdGkiOiJvbnJ0YWM6MDA2Y2YzMzUtMzExMS0yMmQ5LTEyNDUtYmZlOTk3ZWUzNWY0IiwiaXNzIjoiaHR0cDovLzE3Mi4xNy4wLjI6ODA4MC9yZWFsbXMvc2FtcGxlLXJlYWxtIiwic3ViIjoiMDIxMzkyNDEtNTJlZi00MDljLTgzODgtNDU0NTM2ZDRhNWNmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZWUtY2xpZW50Iiwic2lkIjoiR1U0Ykstd0xYN2NWSFc5bUUtTGM2cEhlIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJsZWFkZXItcm9sZSJdfSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicm9sZXMiOlsibGVhZGVyLXJvbGUiXSwibmFtZSI6IkxlYWRlciBVc2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibGVhZGVyLXVzZXIiLCJnaXZlbl9uYW1lIjoiTGVhZGVyIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwiZW1haWwiOiJsZWFkZXItdXNlckBleGFtcGxlLmNvbSJ9.VQJi9k8NAbxhOkNMjKn-_klSF4axdklo_Op65JJ3ReFqtXHBO_2QHdhOyIxmJV7plPuPDQ1CAwYfEavDJ1y1JCZSk7I34Y2YeOCxqXvoeZ6bO9sKSH90mXmZAsqQ_VrsUxVv2uz6yhABCBYIYTSt2BbJEOO-3fA9nf6xcXMUkUYWwjgoBUXHVTVYBMV_oSTOgNgPk645oB5Qj673hXqR5IybRk-7fGnFPwWFpXG4a-sb4XOEnYJkdda_uSdpJ2_e1DS7a4VXgzvtiMn1Vv2q8AWuaSzsZ2gj87t_OO53a2o62X0euAt8n49oJDCFdgkYPTOw5L3TDJkeslnSQUyxEw
access tokens claims
exp = Sun Aug 16 18:08:04 JST 2026
iat = Sun Aug 16 18:03:04 JST 2026
auth_time = 1786870984
jti = onrtac:006cf335-3111-22d9-1245-bfe997ee35f4
iss = http://172.17.0.2:8080/realms/sample-realm
sub = 02139241-52ef-409c-8388-454536d4a5cf
typ = Bearer
azp = ee-client
sid = GU4bK-wLX7cVHW9mE-Lc6pHe
acr = 1
realm_access = {roles=[leader-role]}
scope = openid profile email
email_verified = false
roles = [leader-role]
name = Leader User
preferred_username = leader-user
given_name = Leader
family_name = User
email = leader-user@example.com
id token string = eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJWSDkxSlJURXR5bDZUN1Bkb2Zib0l6STBGREJ1LVlZYl9GU1dObFlzNWhzIn0.eyJleHAiOjE3ODY4NzEyODQsImlhdCI6MTc4Njg3MDk4NCwiYXV0aF90aW1lIjoxNzg2ODcwOTg0LCJqdGkiOiJmYTM3Yzk4Yi0xOTQwLTQ2YTktMzUxOC0wMDkzMWViOTJhMDYiLCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMjo4MDgwL3JlYWxtcy9zYW1wbGUtcmVhbG0iLCJhdWQiOiJlZS1jbGllbnQiLCJzdWIiOiIwMjEzOTI0MS01MmVmLTQwOWMtODM4OC00NTQ1MzZkNGE1Y2YiLCJ0eXAiOiJJRCIsImF6cCI6ImVlLWNsaWVudCIsIm5vbmNlIjoiai1rcnc3NEFmSktFYm0zNVhBMmI2LVVoY1V6cWZVVU0yd1VoRWVDd3Z2MCIsInNpZCI6IkdVNGJLLXdMWDdjVkhXOW1FLUxjNnBIZSIsImF0X2hhc2giOiJsdXRyZ3h6WGF1SWx3WGw0QzN1ZlZnIiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkxlYWRlciBVc2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibGVhZGVyLXVzZXIiLCJnaXZlbl9uYW1lIjoiTGVhZGVyIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwiZW1haWwiOiJsZWFkZXItdXNlckBleGFtcGxlLmNvbSJ9.vGz7tiHWtweSNVjn6DMPQ1GduvQX0vtmUy1xhDwnQqsAOP8jIWTmbpPYpojIDAFQpncQ79D_XwSJQkSIhHdRBg-1MaPbAF0u-W8NHe8dcsTPQSmp6in0RC5X-PPWPsjGlwsJwR2tRhvGx-rUPWfH0qQYyK8qsfObVurQTY00fHdMF0m1uKOvp0LjHL36g6dhO7GvLJSX8ZqnkJWlXcExTW_ESnmWADJ4P5I3a0WZKCzy1JEGhaAFE7ZJpT2D_Sj8usyMP-zSp58PJTaY1GlQkClMVH9OMA_lGLYAdnHs7KWL5Gay_uTdjQ0jGDHJe9n1K22_lUjEMwyesOGm8uQrpg
id tokens claims
exp = Sun Aug 16 18:08:04 JST 2026
iat = Sun Aug 16 18:03:04 JST 2026
auth_time = 1786870984
jti = fa37c98b-1940-46a9-3518-00931eb92a06
iss = http://172.17.0.2:8080/realms/sample-realm
aud = [ee-client]
sub = 02139241-52ef-409c-8388-454536d4a5cf
typ = ID
azp = ee-client
nonce = j-krw74AfJKEbm35XA2b6-UhcUzqfUUM2wUhEeCwvv0
sid = GU4bK-wLX7cVHW9mE-Lc6pHe
at_hash = lutrgxzXauIlwXl4C3ufVg
acr = 1
email_verified = false
name = Leader User
preferred_username = leader-user
given_name = Leader
family_name = User
email = leader-user@example.com
=============== OpenIdContext ===============
次に一般ユーザーのみがアクセスできるhttp://localhost:8080/general-onlyを表示してみると、Forbiddenになります。

あとは表示内容は記載しませんが、アクセス結果を書いておきます。
- /leader-only … アクセス可能、/leader-or-generalと同じ結果が表示される
- /plain … アクセス可能、/leader-or-generalと同じ結果が表示される
- /deny … Forbidden
次は一般ユーザーでアクセスします。
未ログイン状態でhttp://localhost:8080/leader-or-generalへアクセスして、Keycloakへログイン。

結果。

=============== Principal ===============
name = general-user
=============== Principal ===============
=============== OpenIdContext ===============
subject = 099146a0-ee62-42bc-81d3-5c1611f14a58
claims preferred_username = general-user
claims roles = [general-role]
access token string = eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJWSDkxSlJURXR5bDZUN1Bkb2Zib0l6STBGREJ1LVlZYl9GU1dObFlzNWhzIn0.eyJleHAiOjE3ODY4NzE0NjcsImlhdCI6MTc4Njg3MTE2NywiYXV0aF90aW1lIjoxNzg2ODcxMTY3LCJqdGkiOiJvbnJ0YWM6MmZlMzU2NTEtNGQzNi0zMDU5LTFmMjEtZjczYzVlMzkzOTM5IiwiaXNzIjoiaHR0cDovLzE3Mi4xNy4wLjI6ODA4MC9yZWFsbXMvc2FtcGxlLXJlYWxtIiwic3ViIjoiMDk5MTQ2YTAtZWU2Mi00MmJjLTgxZDMtNWMxNjExZjE0YTU4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZWUtY2xpZW50Iiwic2lkIjoiV05HbTRXcmJnYUJVZ19UMkRRSXpPWjc1IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJnZW5lcmFsLXJvbGUiXX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInJvbGVzIjpbImdlbmVyYWwtcm9sZSJdLCJuYW1lIjoiR2VuZXJhbCBVc2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZ2VuZXJhbC11c2VyIiwiZ2l2ZW5fbmFtZSI6IkdlbmVyYWwiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJlbWFpbCI6ImdlbmVyYWwtdXNlckBleGFtcGxlLmNvbSJ9.ALL49UBfRui5lVokApy7myqt08TO6VfOspwmoULMu3zfzu9FEFzGjc0oQeg_GII0CchaZix9glKmZTCN13xtMarpjsb1o-O-ReUhKvkGZ_0p0oTm_r51HHd7HKoLjfT1RjsG-unVgxPM_VAgVarRMToIsIwfjNgzy5-927VgqBn25E-Dc0Mx-cjIRs8XWFwgfGiAb_6ncyQBixul3LSohGFJsRblFvwpFUfsJqiLFku8BfML_Xx4HdiuK91gVukQa3uoGQPEsbrcLix5zwpLi-5pAZ0nCWRjsftQ05Nhr2S335N6XaF4g5T13BpFFCKSPIf0lpFwEz7Nr_cjbtpP7A
access tokens claims
exp = Sun Aug 16 18:11:07 JST 2026
iat = Sun Aug 16 18:06:07 JST 2026
auth_time = 1786871167
jti = onrtac:2fe35651-4d36-3059-1f21-f73c5e393939
iss = http://172.17.0.2:8080/realms/sample-realm
sub = 099146a0-ee62-42bc-81d3-5c1611f14a58
typ = Bearer
azp = ee-client
sid = WNGm4WrbgaBUg_T2DQIzOZ75
acr = 1
realm_access = {roles=[general-role]}
scope = openid profile email
email_verified = false
roles = [general-role]
name = General User
preferred_username = general-user
given_name = General
family_name = User
email = general-user@example.com
id token string = eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJWSDkxSlJURXR5bDZUN1Bkb2Zib0l6STBGREJ1LVlZYl9GU1dObFlzNWhzIn0.eyJleHAiOjE3ODY4NzE0NjcsImlhdCI6MTc4Njg3MTE2NywiYXV0aF90aW1lIjoxNzg2ODcxMTY3LCJqdGkiOiIzMmJlYjA4Mi1hMjEyLWM5MTctOGE4MC02NjllYTliODdlMmMiLCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMjo4MDgwL3JlYWxtcy9zYW1wbGUtcmVhbG0iLCJhdWQiOiJlZS1jbGllbnQiLCJzdWIiOiIwOTkxNDZhMC1lZTYyLTQyYmMtODFkMy01YzE2MTFmMTRhNTgiLCJ0eXAiOiJJRCIsImF6cCI6ImVlLWNsaWVudCIsIm5vbmNlIjoiMnA5QVdSbG9ySVYxZVEtR2VzUi1hb0owMkFkeWY5Zjc5TXNJY01FYnEzTSIsInNpZCI6IldOR200V3JiZ2FCVWdfVDJEUUl6T1o3NSIsImF0X2hhc2giOiI1SFl1eEhqUVhRSHFsQjd0YUQ1eUh3IiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkdlbmVyYWwgVXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImdlbmVyYWwtdXNlciIsImdpdmVuX25hbWUiOiJHZW5lcmFsIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwiZW1haWwiOiJnZW5lcmFsLXVzZXJAZXhhbXBsZS5jb20ifQ.OmviauOCHScXA3-hF3v67ms4NyYrJgIlGBK0tpIhV_cx520tq4q1-wj9YH-WlbsYoP2bLA0Yfm9Y1Y9dUNY12UoYkEGG8eG8IN4KJ8Z1RohtKNLNuhpcvYWDVtru8U9qoVkDPd3H5jjkk7uG5T9f03KVVtL8e8RzdyBotQPg7QjOtfRMAy-p5aDe7de-OJuYwg3lxtt0aoeV0u3QJdeLOVaFYDzr7fK2R2F1rAAiWUQTIybjfo4Uzi_gblL415VljzpCx1t7S9ASkvCj-LjMN02z9ZIDPoTpmpoucf53yElhjZLODFdpLMD5uMhhNL1vbgg-Xo7rOsRqp2q5d6klKg
id tokens claims
exp = Sun Aug 16 18:11:07 JST 2026
iat = Sun Aug 16 18:06:07 JST 2026
auth_time = 1786871167
jti = 32beb082-a212-c917-8a80-669ea9b87e2c
iss = http://172.17.0.2:8080/realms/sample-realm
aud = [ee-client]
sub = 099146a0-ee62-42bc-81d3-5c1611f14a58
typ = ID
azp = ee-client
nonce = 2p9AWRlorIV1eQ-GesR-aoJ02Adyf9f79MsIcMEbq3M
sid = WNGm4WrbgaBUg_T2DQIzOZ75
at_hash = 5HYuxHjQXQHqlB7taD5yHw
acr = 1
email_verified = false
name = General User
preferred_username = general-user
given_name = General
family_name = User
email = general-user@example.com
=============== OpenIdContext ===============
他のパスへのアクセス結果も記載しておきます。
- /leader-only … Forbidden
- /general-only … アクセス可能、/leader-or-generalと同じ結果が表示される
- /plain … アクセス可能、/leader-or-generalと同じ結果が表示される
- /deny … Forbidden
ちなみに、未ログイン状態だと以下のようになります。
- /plain … 最初に書いたように名前だけ表示される
- /deny … Forbidden
- その他のページはKeycloakへ
というわけで、設定どおり動きました。
オマケ
WildFlyではJASPIとのインテグレーションを無効にする
WildFly Maven Pluginで設定していた以下の部分ですが、こちらはJASPIを無効にして資格情報の検証を無効にする設定です。
<commands> <command>/subsystem=undertow/application-security-domain=other:write-attribute(name=integrated-jaspi, value=false)</command> </commands>
やっておかないと、Keycloakにログインしてアプリケーション側に戻ってきた時にこういう例外を見ることになります。
16:06:44,573 ERROR [io.undertow.request] (default task-2) UT005023: Exception handling request to /callback: java.lang.IllegalStateException: java.io.IOException: java.io.IOException: ELY01177: Authorization failed. at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.jaspic.Jaspic.handleCallbacks(Jaspic.java:172) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.jaspic.Jaspic.notifyContainerAboutLogin(Jaspic.java:145) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.HttpMessageContextImpl.notifyContainerAboutLogin(HttpMessageContextImpl.java:220) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.HttpMessageContextImpl.notifyContainerAboutLogin(HttpMessageContextImpl.java:198) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.OpenIdAuthenticationMechanism.validateAuthorizationCode(OpenIdAuthenticationMechanism.java:379) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.OpenIdAuthenticationMechanism.authenticate(OpenIdAuthenticationMechanism.java:287) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.OpenIdAuthenticationMechanism.validateRequest(OpenIdAuthenticationMechanism.java:175) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.OpenIdAuthenticationMechanism$Proxy$_$$_WeldClientProxy.validateRequest(Unknown Source) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:565) at org.jboss.weld.core@6.0.4.Final//org.jboss.weld.bean.proxy.AbstractBeanInstance.invoke(AbstractBeanInstance.java:39) at org.jboss.weld.core@6.0.4.Final//org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:109) at org.glassfish.soteria@4.0.2//org.jboss.weld.generated.proxies.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism$1008466176$Proxy$_$$_WeldClientProxy.validateRequest(Unknown Source) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.DefaultHttpAuthenticationMechanismHandler.validateRequest(DefaultHttpAuthenticationMechanismHandler.java:39) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:565) at org.jboss.weld.core@6.0.4.Final//org.jboss.weld.bean.proxy.AbstractBeanInstance.invoke(AbstractBeanInstance.java:39) at org.jboss.weld.core@6.0.4.Final//org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:109) at org.glassfish.soteria@4.0.2//org.jboss.weld.generated.proxies.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanismHandler$421417595$Proxy$_$$_WeldClientProxy.validateRequest(Unknown Source) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.jaspic.HttpBridgeServerAuthModule.validateRequest(HttpBridgeServerAuthModule.java:87) at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.ElytronServerAuthContext.validateRequest(ElytronServerAuthContext.java:85) at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.WrappingServerAuthContext.lambda$validateRequest$0(WrappingServerAuthContext.java:50) at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.ThreadLocalCallbackHandler.get(ThreadLocalCallbackHandler.java:56) at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.WrappingServerAuthContext.validateRequest(WrappingServerAuthContext.java:50) at org.wildfly.security.elytron-web.undertow-server-servlet@4.2.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ServletSecurityContextImpl.authenticate(ServletSecurityContextImpl.java:180) at org.wildfly.security.elytron-web.undertow-server-servlet@4.2.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ServletSecurityContextImpl.authenticate(ServletSecurityContextImpl.java:102) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:55) at io.undertow.core@2.4.2.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.core@2.4.2.Final//io.undertow.security.handlers.AuthenticationConstraintHandler.handleRequest(AuthenticationConstraintHandler.java:53) at io.undertow.core@2.4.2.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler.handleRequest(ServletSecurityConstraintHandler.java:60) at io.undertow.core@2.4.2.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at org.wildfly.security.elytron-web.undertow-server-servlet@4.2.1.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38) at io.undertow.core@2.4.2.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at org.wildfly.extension.undertow@41.0.0.Final//org.wildfly.extension.undertow.deployment.ControlPointDeploymentInfoConfigurator$ControlPointRequestAttributeHandler.handleRequest(ControlPointDeploymentInfoConfigurator.java:175) at org.wildfly.extension.undertow@41.0.0.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler$1$1.handleRequest(JACCContextIdHandler.java:74) at io.undertow.core@2.4.2.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) at io.undertow.core@2.4.2.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:278) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.call(ServletInitialHandler.java:131) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.call(ServletInitialHandler.java:128) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at org.wildfly.extension.undertow@41.0.0.Final//org.wildfly.extension.undertow.deployment.ControlPointDeploymentInfoConfigurator$1.call(ControlPointDeploymentInfoConfigurator.java:100) at org.wildfly.extension.undertow@41.0.0.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler$2$1.call(JACCContextIdHandler.java:103) at org.wildfly.extension.undertow@41.0.0.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1428) at org.wildfly.extension.undertow@41.0.0.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1428) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:258) at io.undertow.servlet@2.0.1.Final//io.undertow.servlet.handlers.ServletInitialHandler.lambda$new$1(ServletInitialHandler.java:100) at io.undertow.core@2.4.2.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:368) at io.undertow.core@2.4.2.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:935) at org.jboss.threads@3.9.2//org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18) at org.jboss.threads@3.9.2//org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2651) at org.jboss.threads@3.9.2//org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2630) at org.jboss.threads@3.9.2//org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1622) at org.jboss.threads@3.9.2//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1589) at org.jboss.xnio@3.8.16.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282) at java.base/java.lang.Thread.run(Thread.java:1474) Caused by: java.io.IOException: java.io.IOException: ELY01177: Authorization failed. at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.JaspiAuthenticationContext$1.handle(JaspiAuthenticationContext.java:143) at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.ThreadLocalCallbackHandler.handle(ThreadLocalCallbackHandler.java:49) at org.glassfish.soteria@4.0.2//org.glassfish.soteria.mechanisms.jaspic.Jaspic.handleCallbacks(Jaspic.java:166) ... 59 more Caused by: java.io.IOException: ELY01177: Authorization failed. at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.JaspiAuthenticationContext$1.handleOne(JaspiAuthenticationContext.java:255) at org.wildfly.security.jakarta.authentication@4.0.0.Final//org.wildfly.security.auth.jaspi.impl.JaspiAuthenticationContext$1.handle(JaspiAuthenticationContext.java:134) ... 61 more
出典はAuth0のブログです。
Use Jakarta EE 10 with OpenID Connect Authentication
PKCE
Keycloakでは、PKCEを強制できるように設定できます。
resource "keycloak_openid_client" "ee_client" { realm_id = keycloak_realm.sample_realm.id client_id = "ee-client" name = "Jakarta Security Client" enabled = true access_type = "CONFIDENTIAL" standard_flow_enabled = true direct_access_grants_enabled = true # テスト用 pkce_code_challenge_method = "S256" # PKCE root_url = "http://localhost:8080" valid_redirect_uris = ["http://localhost:8080/*"] }
Jakarta Securityでは特にPKCEに言及がなく、Soteriaとしてもどうなっているのだろうとちょっと試してみました。
これをやるとどうなるかですが、ログインが必要なページにアクセスしてKeycloakにリダイレクトした時に失敗します。
つまり、Keycloakのログイン画面が見えなくなります。
Keycloak側のログ。
2026-08-16 09:13:28,920 INFO [org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker] (executor-thread-63) PKCE enforced Client without code challenge method. 2026-08-16 09:13:28,920 WARN [org.keycloak.events] (executor-thread-63) type="LOGIN_ERROR", realmId="sample-realm", realmName="sample-realm", clientId="ee-client", userId="null", ipAddress="172.17.0.1", error="invalid_request", reason="Missing parameter: code_challenge_method", response_type="code", redirect_uri="http://localhost:8080/callback", response_mode="query" 2026-08-16 09:13:28,920 INFO [org.keycloak.http.access-log] (executor-thread-63) 172.17.0.1 - - [16/Aug/2026:09:13:28 +0000] "GET /realms/sample-realm/protocol/openid-connect/auth?scope=openid+email+profile&response_type=code&client_id=ee-client&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&state=93da40f0-6d37-4166-9f6a-7bfac292033b&nonce=JYVAuEsWO7n78lDPOyxIGMpvrqbqEutOg-7KkjvtMrU&display=page HTTP/1.1" 302 -
まあ、仕様書に記載がないのでうまくいかないであろうことと、Soteriaのソースコードも対応指定なさそうな雰囲気を
感じたので、そうでしょうね、というところではありますが。
クライアントプロトコルマッパー
こちらは前回のエントリーを参照…。
WildFly 34とKeycloak 26でJakarta SecurityのOpenID Connectを使った認証と認可設定を試す - CLOVER🍀
おわりに
Jakarta Securityを使ったOpenID Connectを使った認証、それから認可設定を試してみました。
前回はとてもとても苦労しましたが、1回やっておいたのでなんとかなりましたね…。ほとんどそのまま使える情報で
よかったです…。