Contents
Keycloak クライアント設定の概要
Keycloak で OIDC クライアントを作成し、Spring Boot アプリ側と正しく連携させることが最初のハードルです。このセクションでは Client ID/Secret の取得方法 と リダイレクト URI・スコープ設定 を中心に説明します。
クライアント作成手順
以下は Keycloak 管理コンソールで新規クライアントを追加する標準的なフローです。
-
ログイン → Realm 選択
コンソールにアクセスし、対象の realm(例:myrealm)を選択します。 -
[Clients] > [Create] をクリック
- Client ID: 任意の英数字(例:
spring-boot-app) - Client Protocol:
openid-connectを必ず選択 -
Root URL: アプリのベース URL(例:
http://localhost:8080) -
保存後に表示される「Credentials」タブでシークレットを取得
「Regenerate Secret」ボタンで新しいシークレットが生成できます。取得した文字列は 環境変数やシークレットマネージャー に安全に保管してください。 -
Valid Redirect URIs の設定
http://localhost:8080/login/oauth2/code/keycloak
複数の環境がある場合は改行またはカンマで区切って列挙できます(Keycloak 24 系では改行推奨)。 -
スコープと PKCE の有効化
- Advanced Settings → Proof Key for Code Exchange (PKCE) を ON にします。Spring Security が自動でコードチャレンジ/ベリファイを生成します。
- Scope には最低でも
openid,profile,emailの3つを含めます。
ポイント:Keycloak の UI はバージョンごとに配置が変わることがありますが、上記項目はすべて 「Clients」→「Create/Edit」画面 に必ず存在します。
シークレット取得と管理のベストプラクティス
- 環境変数(例:
KEYCLOAK_CLIENT_SECRET)や HashiCorp Vault / AWS Secrets Manager を利用し、リポジトリに平文を残さない。 - 開発・本番で別々のシークレットを作成し、realm の Client Scopes で環境ごとに切り替えると安全性が向上します。
リダイレクト URI とスコープ設定の注意点
| 項目 | 推奨値・説明 |
|---|---|
| Valid Redirect URIs | 正規表現は使用できません。完全な URL を列挙してください。http://localhost:8080/** のようにワイルドカードを入れると予期せぬリダイレクト先が許可される危険があります。 |
| Scope | openid が必須です。追加スコープはアプリ側で必要になるまで最小限に抑えると、トークンサイズの肥大化を防げます。 |
Spring Boot 3.x と Spring Security 6 の依存関係・ビルド設定
Spring Boot 3 系は Jakarta EE 9 に移行したため、一部パッケージ名が変更されています。このセクションでは、Keycloak OIDC を利用するために必要なスターターと、Jakarta 移行時の留意点を解説します。
必要なスターター(Gradle / Maven)
以下の依存関係だけで、OAuth2 クライアント機能とリソースサーバー機能が利用可能です。
Gradle (Kotlin DSL)
|
1 2 3 4 5 6 7 8 9 |
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") // OIDC(Authorization Code Flow + PKCE)を提供 implementation("org.springframework.boot:spring-boot-starter-oauth2-client") implementation("org.springframework.boot:spring-boot-starter-security") // JWT デコードやリソースサーバーとしての機能が必要な場合 implementation("org.springframework.security:spring-security-oauth2-resource-server") } |
Maven
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> </dependency> </dependencies> |
Jakarta EE 移行時の注意点
- Servlet API が
jakarta.servlet.*に変わったため、サードパーティライブラリが古いjavax.servlet.*を使用している場合はバージョンアップが必要です。 - Spring Boot 3.2 以降ではデフォルトで Tomcat 10(Jakarta 対応)が組み込まれます。特別な設定は不要ですが、独自の
Filterを実装する際はインポート先を確認してください。
application.yml に記述する OIDC 設定例
Spring Boot は issuer‑uri から .well-known/openid-configuration を自動取得します。このセクションでは、最小構成と拡張設定の両方を示し、PKCE の有効化や環境変数管理のベストプラクティスも合わせて解説します。
基本構成(最小サンプル)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
spring: security: oauth2: client: registration: keycloak: # 任意名、login URL の一部として利用される client-id: spring-boot-app client-secret: ${KEYCLOAK_CLIENT_SECRET} authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" scope: - openid - profile - email provider: keycloak: issuer-uri: http://localhost:8080/realms/myrealm |
解説ポイント
issuer-uriが Keycloak の realm エンドポイント を指すと、Spring は自動で認可・トークンエンドポイント等を取得します。{baseUrl}と{registrationId}プレースホルダーにより、環境ごとのベース URL に柔軟に対応できます(例:http://localhost:8080→https://api.example.com)。
PKCE の有効化はデフォルトで自動
Spring Security 6.1 以降、Authorization Code Flow を使用した場合は内部的に PKCE が有効になります。明示的な設定項目は不要ですが、以下の点だけは確認してください。
- クライアント側(ブラウザ)で
code_challenge_method=S256が送信されているか。 - Keycloak 側で Advanced Settings → PKCE が ON になっていること。
環境変数・外部設定のベストプラクティス
| 項目 | 推奨手法 |
|---|---|
| シークレット | application.yml では ${ENV_VAR} の形で参照し、実際の値は OS 環境変数またはコンテナのシークレット機構に委譲。 |
| Issuer URI | 本番環境と開発環境で異なる場合は spring.profiles.active を利用してプロファイルごとに上書き。 |
| ロギングレベル | デバッグ時は org.springframework.security=DEBUG に設定し、認可リクエストのパラメータを確認できるようにする。 |
Authorization Code Flow with PKCE の詳細とトラブルシューティング
PKCE を組み込んだ Authorization Code Flow はブラウザ側でコードチャレンジ/ベリファイを行うため、認可コードが漏洩しても攻撃者はアクセストークンを取得できません。ここではフロー全体像と、実務でよく遭遇するエラーへの対処法をまとめます。
フロー全体図(テキスト版)
- 未認証リクエスト →
OAuth2LoginAuthenticationFilterが Keycloak のauthorization_endpointにリダイレクト。 - PKCE パラメータ付与 → ランダムな
code_verifierと SHA‑256 で生成したcode_challengeを認可リクエストに添付。 - ユーザー認証・同意 → Keycloak が
redirect-uriにcode(認可コード)を返す。 - アクセストークン交換 → Spring の内部クライアント (
OAuth2AccessTokenResponseClient) がcode_verifierと共にトークンエンドポイントへ POST。 - トークン受領 & 保存 → 取得した
access_token,refresh_token,id_tokenをOAuth2AuthorizedClientServiceに保存。 - 自動リフレッシュ → アクセストークンが期限切れになると、同サービスが
refresh_tokenを使って新トークンを取得し、以降のリクエストに自動付与。
主なエラーと対処法(表)
| エラーコード | 発生シーン | 主な原因 | 推奨対策 |
|---|---|---|---|
| 400 Bad Request (invalid_grant) | トークン交換時 | code_verifier がサーバ側で受領した code_challenge と不一致 |
PKCE 生成ロジックが変更されていないか、フレームワークのバージョン差異を確認 |
| 401 Unauthorized (invalid_client) | 認可コード取得またはトークン交換時 | クライアントシークレットが間違っている/ヘッダー未送付 | 環境変数 KEYCLOAK_CLIENT_SECRET の内容と application.yml 参照先を再確認 |
| 403 Forbidden (insufficient_scope) | アクセス対象 API 呼び出し時 | 必要なスコープがトークンに含まれていない | scope に openid profile email が必ず入っているか、Keycloak のクライアントスコープ設定を点検 |
| 500 Internal Server Error | Keycloak 側で例外発生 | カスタムプロトコルマッパーやユーザ属性の不整合 | サーバーログでスタックトレースを確認し、カスタムクレームの定義を見直す |
トークンリフレッシュと保存先
- デフォルト実装は インメモリ (
InMemoryOAuth2AuthorizedClientService) です。スケールアウトや再起動後もトークンを保持したい場合は、JdbcOAuth2AuthorizedClientServiceまたはRedisOAuth2AuthorizedClientServiceに差し替えることが推奨されます。 - リフレッシュトークンの有効期限 は Keycloak の Realm Settings → Tokens で設定できます。長すぎるとセキュリティリスクになるため、必要最小限に留めましょう(例: 30 日)。
カスタム JWT 認証とロールマッピング
Keycloak が発行する JWT には realm_access.roles と resource_access.<client>.roles が格納されます。Spring Security の GrantedAuthority に変換して、アプリ側の認可ロジックで利用できるようにします。
JwtAuthenticationConverter 実装例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.example.security; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; public class KeycloakJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> { private static final String REALM_ROLE_CLAIM = "realm_access"; private static final String RESOURCE_ACCESS_CLAIM = "resource_access"; @Override public JwtAuthenticationToken convert(Jwt jwt) { Collection<SimpleGrantedAuthority> authorities = extractRoles(jwt); return new JwtAuthenticationToken(jwt, authorities); } @SuppressWarnings("unchecked") private Collection<SimpleGrantedAuthority> extractRoles(Jwt jwt) { // 1. realm ロール取得 List<String> realmRoles = Optional.ofNullable(jwt.getClaimAsMap(REALM_ROLE_CLAIM)) .map(m -> (List<String>) m.getOrDefault("roles", Collections.emptyList())) .orElse(Collections.emptyList()); // 2. クライアントロール取得(client-id が spring-boot-app の場合) Map<String, Object> resourceAccess = jwt.getClaimAsMap(RESOURCE_ACCESS_CLAIM); List<String> clientRoles = Optional.ofNullable(resourceAccess) .map(m -> (Map<String, Object>) m.get("spring-boot-app")) .map(c -> (List<String>) c.getOrDefault("roles", Collections.emptyList())) .orElse(Collections.emptyList()); // 3. ROLE_ プレフィックス付与して GrantedAuthority に変換 return Stream.concat(realmRoles.stream(), clientRoles.stream()) .map(r -> new SimpleGrantedAuthority("ROLE_" + r.toUpperCase())) .collect(Collectors.toSet()); } } |
実装ポイント
realm_access.rolesとresource_access.<client>.rolesの取得は Map 型 で安全にキャストする必要があります。- ロール名は大文字化し、
ROLE_プレフィックスを付与すると Spring Security のデフォルトロジックと整合します。
SecurityFilterChain への組み込み例(Java Config)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) // OIDC ログイン .oauth2Login(oauth2 -> oauth2 .loginPage("/login") // カスタムログインページがある場合 ) // リソースサーバー(JWT)設定 .oauth2ResourceServer(oauth2rs -> oauth2rs .jwt(jwt -> jwt .jwtAuthenticationConverter(new KeycloakJwtAuthenticationConverter()) ) ); return http.build(); } |
追加検証:aud, iss, exp のチェック
issuer-uri が正しく設定されていれば NimbusJwtDecoder が自動で iss(Issuer) と exp(有効期限) を検証します。さらに以下のようにカスタムバリデータを追加できます。
|
1 2 3 4 5 6 7 8 9 |
@Bean JwtDecoder jwtDecoder() { NimbusJwtDecoder decoder = JwtDecoders.fromIssuerLocation("http://localhost:8080/realms/myrealm"); OAuth2TokenValidator<Jwt> withAudience = new AudienceValidator("my-client-id"); OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer("http://localhost:8080/realms/myrealm"); decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(withIssuer, withAudience)); return decoder; } |
Keycloak アダプタから Spring Security 標準へ移行する手順
Keycloak の旧来の Spring Boot アダプタ (keycloak-spring-boot-starter) はメンテナンスが終了しています。標準的な Spring Security に置き換えることで、長期的な保守コストを削減できます。このセクションでは具体的な移行ステップとチェックリストを示します。
依存除去と設定変換
| 手順 | 内容 |
|---|---|
| 1. ビルドファイルからアダプタ依存を削除 | org.keycloak:keycloak-spring-boot-starter を pom.xml / build.gradle.kts から除去。 |
| 2. application.yml に OIDC 設定を書き換える | 従来の keycloak.realm, resource 等を、前述の Spring Security 用設定 (spring.security.oauth2.client) に置き換える。 |
| 3. KeycloakAdapterConfiguration の削除 | KeycloakWebSecurityConfigurerAdapter を拡張したクラスは不要になるため、削除または SecurityFilterChain に統合する。 |
ロールマッピングの調整
- アダプタでは自動的に
ROLE_が付与されていましたが、標準実装でも同様に JwtAuthenticationConverter で明示的に付与してください(上記コード参照)。 - 既存テストケースがロール名に依存している場合は、テストコードも
ROLE_プレフィックス前提へ更新します。
移行後の動作確認項目
- 認可コード取得とリダイレクト が期待通りに動くか(ブラウザでログイン画面が表示される)。
- アクセストークン取得 後、
Authorization: Bearer <token>ヘッダーで保護エンドポイントへアクセスできるか。 - ロールベース認可 (
@PreAuthorize("hasRole('ADMIN')")) が正しく機能するか。 - トークンリフレッシュ が自動で行われ、
OAuth2AuthorizedClientServiceに新しいトークンが保存されているか。
開発・テスト環境でのデバッグポイントとサンプルリポジトリ
ローカル開発時にハマりやすい設定やログ出力のコツをまとめ、実際に動作する参考実装リポジトリへのリンクも提供します。
CORS とローカル開発用設定
- Keycloak 側 → Realm Settings → Security Defenses → Headers で
Access-Control-Allow-Originにhttp://localhost:3000(フロントエンド)やhttp://localhost:8080を追加。 - Spring Boot 側 →
CorsConfigurationSourceビーンを定義し、全てのエンドポイントに対して同一オリジンを許可します。
|
1 2 3 4 5 6 7 8 9 10 11 |
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("http://localhost:3000")); config.setAllowedMethods(List.of("*")); config.setAllowedHeaders(List.of("*")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } |
ログレベルと診断情報取得
- デバッグ出力を有効化
yaml
logging:
level:
org.springframework.security: DEBUG
org.keycloak.adapters.springboot: INFO # アダプタ未使用時は無視可 - HTTP トレース:
org.springframework.web.client.RestTemplateを使っている場合はlogging.level.org.apache.http=DEBUGで外部リクエストのヘッダーも確認できます。
実際に動くサンプルプロジェクト
以下の GitHub リポジトリは、Keycloak(v24)と Spring Boot 3.x の組み合わせを 完全動作 させた実装例です。README にビルド手順・環境変数設定方法が詳述されています。
- リポジトリ名:
spring-boot-keycloak-oidc-demo - URL: https://github.com/keycloak-examples/spring-boot-keycloak-oidc-demo
サンプルの手順概要
|
1 2 3 4 5 6 7 8 9 10 11 |
# 1. リポジトリ取得 git clone https://github.com/keycloak-examples/spring-boot-keycloak-oidc-demo.git cd spring-boot-keycloak-oidc-demo # 2. 環境変数設定(例: .env ファイルを作成) export KEYCLOAK_CLIENT_SECRET=your-secret export SPRING_PROFILES_ACTIVE=dev # dev プロファイルは localhost 用設定 # 3. アプリ起動(Gradle) ./gradlew bootRun |
注意:リポジトリは公開されている公式サンプルです。実運用に際しては
client-secretを本番環境のシークレットストアへ移行してください。
まとめ
- Keycloak クライアント設定では、Client ID/Secret の取得と PKCE 有効化・スコープ設定を正確に行うことが成功の鍵です。
- Spring Boot 3.x + Spring Security 6 の依存関係は
spring-boot-starter-oauth2-clientとspring-security-oauth2-resource-serverだけで完結し、Jakarta EE 移行時の注意点を踏まえれば問題なくビルドできます。 - application.yml に
issuer-uriを記載すれば、.well‑known エンドポイントから自動取得されるため設定ミスが減ります。PKCE はデフォルトで有効化されているので追加設定は不要です。 - Authorization Code Flow with PKCE の全体像と典型的なエラー(invalid_grant、invalid_client、insufficient_scope)を把握すれば、トラブル時の切り分けが迅速に行えます。
- カスタム JWT 認証では
JwtAuthenticationConverterを実装し、Keycloak のロール情報を Spring Security のGrantedAuthorityに変換することで、ロールベース認可がシームレスに機能します。 - 旧 Keycloak アダプタからの移行は依存除去・設定置換・ロールマッピング調整だけで完了し、標準 Spring Security へ統一できるため保守性が大幅に向上します。
- 開発・テスト時のデバッグポイント(CORS 設定、ログレベル、トークンリフレッシュ保存先)を抑えておけば、ローカル環境でもスムーズに動作確認が行えます。
これらの手順とベストプラクティスに従うことで、Keycloak と Spring Boot の安全かつ拡張性の高い OIDC 統合を実現できます。ぜひ上記サンプルリポジトリをクローンし、ローカル環境で動作確認した後、本番環境へ展開してください。