- Authors
- Name
- 1. パスワード疲労とSSOの登場
- 2. SSO実装アーキテクチャ
- 3. 技術標準比較: SAML vs OAuth 2.0 vs OIDC
- 4. Keycloakコアアーキテクチャ
- 5. プロダクションデプロイ
- 6. 認証(Authentication)フロー
- 7. 認可(Authorization)体系
- 8. Identity BrokeringとSocial Login
- 9. アプリケーション連携実務
- 10. MSA環境のToken Relayパターン
- 11. Admin REST APIの活用
- 12. 高可用性(HA)アーキテクチャ
- 13. モニタリングとオブザーバビリティ
- 14. まとめ
- References
1. パスワード疲労とSSOの登場
現代のビジネス環境で使用するアプリケーションが飛躍的に増加する中、**パスワード疲労(Password Fatigue)は深刻なセキュリティ脅威であり、生産性低下の原因となっている。この問題を解決するために登場したSSO(Single Sign-On)**技術は、たった一度の認証で信頼関係が構築されたすべてのサービスにアクセスできるようにする。
SSOによって得られるコアバリューは以下の通りである。
- MFAの集中管理: 多要素認証を一箇所で管理し、一貫したセキュリティポリシーを適用
- 即時のアクセス権剥奪: 退職者やロール変更時にIdPから一括でアクセスを遮断
- ゼロトラストの礎: 「Never Trust, Always Verify」原則の基盤
2. SSO実装アーキテクチャ
SSOシステムは環境に応じて3つのモデルに分かれる。
| モデル | 方式 | 適合環境 |
|---|---|---|
| エージェントベース | 個々のサーバーにエージェントをインストール | レガシー環境 |
| エージェントレス | API Gateway / Reverse Proxyでトラフィックを制御 | クラウドネイティブ |
| アイデンティティフェデレーション | ドメイン間でアイデンティティを共有 | B2B連携、マルチ組織 |
3. 技術標準比較: SAML vs OAuth 2.0 vs OIDC
| 項目 | SAML 2.0 | OAuth 2.0 | OIDC |
|---|---|---|---|
| 目的 | 認証(Authentication) | 認可(Authorization) | 認証 + 認可 |
| データ形式 | XML (Assertion) | JSON | JSON (JWT) |
| トークン | SAML Assertion | Access Token | ID Token + Access Token |
| 主な用途 | エンタープライズWebアプリ | API権限委譲 | モバイル、SPA、MSA |
| 複雑度 | 高 | 中 | 低 |
**OIDC(OpenID Connect)**はOAuth 2.0の上にアイデンティティレイヤーを載せ、JWT形式で軽量に認証を処理するもので、現代のマイクロサービス環境における事実上の標準である。
4. Keycloakコアアーキテクチャ
KeycloakはSAML、OAuth 2.0、OIDC標準をすべてサポートするオープンソースIAMソリューションである。Red Hatが支援し、CNCFインキュベーティングプロジェクトである。
4.1 コア階層構造
Keycloak Instance
└── Realm(論理的な分離単位、テナント)
├── Clients(認証を要求するアプリケーション)
├── Users(ユーザー)
├── Groups(ユーザーグループ)
├── Roles(Realm Roles + Client Roles)
├── Identity Providers(外部IdP連携)
├── Authentication Flows(認証フロー定義)
└── Authorization Services(きめ細かな認可ポリシー)
- Realm: 独立したテナント空間。
masterRealmは管理用であり、実際のサービスには別途Realmを作成する - Client: OAuth/OIDCクライアントアプリケーション(Confidential / Public / Bearer-only)
- RolesとGroups: 権限管理の中核。Realm Roleは全体スコープ、Client Roleは特定クライアントスコープ
4.2 技術スタック
| コンポーネント | 役割 |
|---|---|
| Quarkus | 高性能Javaフレームワーク基盤ランタイム |
| Infinispan | インメモリ分散キャッシュ(セッション、トークン同期) |
| PostgreSQL(等) | ユーザー、ポリシー等の永続データストレージ |
5. プロダクションデプロイ
5.1 Dockerデプロイ
開発モード(クイックスタート、H2内蔵DB):
docker run --name keycloak -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \
quay.io/keycloak/keycloak:latest \
start-dev
プロダクションモード(TLS + PostgreSQL必須):
docker run --name keycloak -p 8443:8443 -p 9000:9000 -m 2g \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=change_me \
quay.io/keycloak/keycloak:latest start \
--hostname=https://auth.example.com \
--https-certificate-file=/opt/keycloak/conf/cert.pem \
--https-certificate-key-file=/opt/keycloak/conf/key.pem \
--db=postgres \
--db-url=jdbc:postgresql://db-host:5432/keycloak \
--db-username=keycloak \
--db-password=change_me \
--health-enabled=true \
--metrics-enabled=true
5.2 Kubernetes Operatorデプロイ
# CRDおよびOperatorのインストール
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
kubectl create namespace keycloak
kubectl -n keycloak apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.5.4/kubernetes/kubernetes.yml
Keycloak CRマニフェスト:
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: production-kc
namespace: keycloak
spec:
instances: 3
db:
vendor: postgres
host: postgres-db
usernameSecret:
name: keycloak-db-secret
key: username
passwordSecret:
name: keycloak-db-secret
key: password
http:
tlsSecret: keycloak-tls-secret
hostname:
hostname: auth.example.com
proxy:
headers: xforwarded
5.3 keycloak.conf プロダクション設定
# Hostname
hostname=https://auth.example.com
hostname-admin=https://admin.auth.example.com
# Database
db=postgres
db-url=jdbc:postgresql://db:5432/keycloak
db-username=keycloak
db-password=${vault.db-password}
db-pool-max-size=100
# TLS
http-enabled=false
https-port=8443
https-certificate-file=/etc/certs/tls.crt
https-certificate-key-file=/etc/certs/tls.key
# Proxy
proxy-headers=xforwarded
# Cache / Clustering
cache=ispn
# Health & Metrics (port 9000)
health-enabled=true
metrics-enabled=true
# Logging
log=console,file
log-console-output=json
6. 認証(Authentication)フロー
6.1 OIDCエンドポイント
すべてのエンドポイントはWell-Known URLから自動検出が可能である。
GET /realms/{realm}/.well-known/openid-configuration
| エンドポイント | パス |
|---|---|
| Authorization | /realms/{realm}/protocol/openid-connect/auth |
| Token | /realms/{realm}/protocol/openid-connect/token |
| Userinfo | /realms/{realm}/protocol/openid-connect/userinfo |
| Logout | /realms/{realm}/protocol/openid-connect/logout |
| JWKS | /realms/{realm}/protocol/openid-connect/certs |
| Introspect | /realms/{realm}/protocol/openid-connect/token/introspect |
6.2 Authorization Code Flow + PKCE
Webアプリケーション及びSPA/モバイルに推奨される標準フローである。
ステップ1 - 認可リクエスト(ブラウザリダイレクト):
GET /realms/myrealm/protocol/openid-connect/auth?
response_type=code&
client_id=my-app&
redirect_uri=https://myapp.example.com/callback&
scope=openid profile email&
state=random-state-value&
code_challenge=BASE64URL(SHA256(code_verifier))&
code_challenge_method=S256
ステップ2 - トークン交換(サーバーサイド):
curl -X POST \
https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://myapp.example.com/callback" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "code_verifier=ORIGINAL_CODE_VERIFIER"
PKCEはコード傍受攻撃を防止する。code_verifierはクライアントが生成したランダム文字列であり、code_challengeはそのSHA256ハッシュである。トークン交換時に元のcode_verifierを提出して検証する。
6.3 Client Credentials Flow
サービス間(M2M)認証に使用する。
curl -X POST \
https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=my-service" \
-d "client_secret=my-service-secret" \
-d "scope=openid"
ConfidentialクライアントにはビルトインのService Accountがある。Clients、次にmy-service、次にService Account Rolesでロールを割り当てる。
6.4 Clientタイプ選択ガイド
| タイプ | シークレット | 用途 |
|---|---|---|
| Confidential | あり | サーバーサイドWebアプリ、バックエンドサービス |
| Public | なし | SPA、モバイルアプリ(PKCE必須) |
| Bearer-only | 検証のみ | REST APIサービス(Resource Server) |
7. 認可(Authorization)体系
7.1 RBAC(ロールベースアクセス制御)
ロールベースアクセス制御は、ユーザーにロールを割り当て、ロールに基づいてアクセスを許可/拒否する。
- Realm Role: Realm全体のスコープ(例:
admin,user) - Client Role: 特定クライアントのスコープ(例:
my-app:editor) - Composite Role: 複数のロールをまとめた上位ロール
7.2 ABAC(属性ベースアクセス制御)
属性ベースアクセス制御は、ユーザーのIP、アクセス時刻、部署情報などを総合して動的に判断する。
Keycloak Authorization Servicesは多様なポリシータイプをサポートする。
| ポリシータイプ | 説明 |
|---|---|
| Role-based | Realm/Clientロールの評価 |
| Group-based | グループメンバーシップの評価 |
| Time-based | 時間帯/期間の制限 |
| Client-based | リクエストクライアントの制限 |
| JavaScript | カスタムJS条件 |
| Regex | パターンマッチング |
| Aggregated | 複数ポリシーの組み合わせ |
7.3 権限モデル
「X(ユーザー)がY(アクション)をZ(リソース)に実行できるか?」
# RPT(Requesting Party Token)リクエストで権限を確認
curl -X POST \
https://auth.example.com/realms/myrealm/protocol/openid-connect/token \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
-d "audience=my-resource-server"
決定戦略:
- Affirmative: 1つのポリシーでも許可すればアクセス許可
- Unanimous: すべてのポリシーが許可した場合のみアクセス許可
8. Identity BrokeringとSocial Login
Keycloakは外部IdPとの連携をネイティブでサポートする。
8.1 対応IdP
| 種類 | サポート一覧 |
|---|---|
| Social Login | Google, GitHub, Facebook, Microsoft, LinkedIn, GitLab等 |
| プロトコルベース | OIDC v1.0, OAuth 2.0, SAML 2.0 |
8.2 Google IdP設定例
- Google Cloud ConsoleでOAuth 2.0認証情報を作成
- Redirect URI:
https://auth.example.com/realms/{realm}/broker/google/endpoint - Keycloak Admin Console、Identity Providers、Googleを追加
- Client IDとClient Secretを入力
8.3 LDAP/Active Directory連携
User Federationを通じてLDAPと連携できる。
| 設定 | Active Directory | 一般LDAP |
|---|---|---|
| Connection URL | ldaps://ad-server:636 | ldap://ldap-server:389 |
| Users DN | OU=Users,DC=corp,DC=example,DC=com | ou=People,dc=example,dc=com |
| Username Attribute | sAMAccountName | uid |
| UUID Attribute | objectGUID | entryUUID |
同期モード:
- READ_ONLY: LDAPからの読み取り専用(最も安全)
- WRITABLE: KeycloakからLDAP属性の変更が可能
- UNSYNCED: 変更をKeycloakローカルにのみ保存
9. アプリケーション連携実務
9.1 Spring Boot Resource Server
application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com/realms/myrealm
jwk-set-uri: https://auth.example.com/realms/myrealm/protocol/openid-connect/certs
SecurityConfig.java:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthoritiesClaimName("realm_access.roles");
converter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
}
9.2 Next.js (Auth.js / NextAuth.js)
.env.local:
AUTH_KEYCLOAK_ID=my-nextjs-app
AUTH_KEYCLOAK_SECRET=my-client-secret
AUTH_KEYCLOAK_ISSUER=https://auth.example.com/realms/myrealm
auth.ts (Auth.js v5):
import NextAuth from 'next-auth'
import Keycloak from 'next-auth/providers/keycloak'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Keycloak],
})
KeycloakのClient設定でRedirect URIを登録する:
https://myapp.example.com/api/auth/callback/keycloak
9.3 React SPA (keycloak-js)
import Keycloak from 'keycloak-js'
const keycloak = new Keycloak({
url: 'https://auth.example.com',
realm: 'myrealm',
clientId: 'my-spa',
})
// Authorization Code Flow + PKCEで初期化
const authenticated = await keycloak.init({
onLoad: 'login-required',
pkceMethod: 'S256',
checkLoginIframe: false,
})
// API呼び出し時にトークンを使用
const response = await fetch('/api/data', {
headers: {
Authorization: `Bearer ${keycloak.token}`,
},
})
// トークン期限切れ前の自動更新
keycloak.onTokenExpired = () => {
keycloak.updateToken(30).catch(() => keycloak.login())
}
// ロール確認
keycloak.hasRealmRole('admin')
keycloak.hasResourceRole('editor', 'my-resource-server')
9.4 Node.js Express
const express = require('express')
const session = require('express-session')
const Keycloak = require('keycloak-connect')
const app = express()
const memoryStore = new session.MemoryStore()
app.use(
session({
secret: 'my-secret',
resave: false,
saveUninitialized: true,
store: memoryStore,
})
)
const keycloak = new Keycloak(
{ store: memoryStore },
{
realm: 'myrealm',
'auth-server-url': 'https://auth.example.com/',
resource: 'my-node-app',
}
)
app.use(keycloak.middleware())
// 認証必須エンドポイント
app.get('/api/protected', keycloak.protect(), (req, res) => {
res.json({ message: 'Authenticated!' })
})
// 特定ロール必須
app.get('/api/admin', keycloak.protect('realm:admin'), (req, res) => {
res.json({ message: 'Admin access granted' })
})
app.listen(3000)
10. MSA環境のToken Relayパターン
マイクロサービスアーキテクチャにおいて、API GatewayがKeycloakと連携してJWTトークンを取得した後、内部サービスにリクエストを転送するパターンである。
[Client] → [API Gateway] → [Keycloak](トークン取得/検証)
↓
Authorization: Bearer <JWT>
↓
[Service A] → [Service B] → [Service C]
各サービスがIdPと直接通信する必要がなく、JWTの署名のみをローカルで検証するため、優れたパフォーマンスとセキュリティを確保できる。KeycloakのJWKSエンドポイント(/protocol/openid-connect/certs)から公開鍵を取得してキャッシュすればよい。
11. Admin REST APIの活用
11.1 トークン取得
# パスワード方式
ACCESS_TOKEN=$(curl -s -X POST \
"https://auth.example.com/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin-password" | jq -r '.access_token')
# Service Account方式(自動化推奨)
ACCESS_TOKEN=$(curl -s -X POST \
"https://auth.example.com/realms/master/protocol/openid-connect/token" \
-d "grant_type=client_credentials" \
-d "client_id=my-admin-client" \
-d "client_secret=my-admin-secret" | jq -r '.access_token')
11.2 ユーザー管理
# ユーザー作成
curl -X POST \
"https://auth.example.com/admin/realms/myrealm/users" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"email": "newuser@example.com",
"enabled": true,
"credentials": [{
"type": "password",
"value": "temp-password",
"temporary": true
}]
}'
# ユーザー検索
curl -X GET \
"https://auth.example.com/admin/realms/myrealm/users?username=johndoe" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# ロール割り当て
curl -X POST \
"https://auth.example.com/admin/realms/myrealm/users/{user-id}/role-mappings/realm" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '[{"id": "ROLE_ID", "name": "admin"}]'
12. 高可用性(HA)アーキテクチャ
12.1 分散アーキテクチャ
┌─────────────┐
│ Load Balancer│
└──────┬──────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Keycloak 1│ │Keycloak 2│ │Keycloak 3│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ Infinispan │ (セッション同期) │
└──────────────┴───────────────┘
│
┌──────┴──────┐
│ PostgreSQL │
│ (共有DB) │
└─────────────┘
- 静的データ(ユーザー、ポリシー): 共有DB(PostgreSQL)に保存
- 動的データ(セッション、トークン): Infinispan分散キャッシュによりノード間で同期
12.2 Kubernetes Health Probe設定
spec:
containers:
- name: keycloak
livenessProbe:
httpGet:
path: /health/live
port: 9000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 9000
initialDelaySeconds: 30
periodSeconds: 10
startupProbe:
httpGet:
path: /health/started
port: 9000
failureThreshold: 30
periodSeconds: 5
12.3 Cross-DCレプリケーション(外部Infinispan)
地理的に分離されたデータセンター間の同期が必要な場合:
cache=ispn
cache-remote-host=infinispan.example.com
cache-remote-port=11222
cache-remote-username=keycloak
cache-remote-password=change_me
cache-remote-tls-enabled=true
13. モニタリングとオブザーバビリティ
13.1 Prometheusメトリクス
# prometheus.yml
scrape_configs:
- job_name: 'keycloak'
metrics_path: '/metrics'
scheme: https
static_configs:
- targets: ['keycloak-host:9000']
イベントメトリクスの有効化:
bin/kc.sh start \
--metrics-enabled=true \
--event-metrics-user-enabled=true \
--event-metrics-user-tags=realm,client,idp \
--event-metrics-user-events=LOGIN,LOGIN_ERROR,LOGOUT,TOKEN_REFRESH
13.2 Grafanaダッシュボード
Keycloakは公式Grafanaダッシュボードを提供している。
| ダッシュボード | 用途 |
|---|---|
keycloak-troubleshooting-dashboard.json | SLIおよび診断分析 |
keycloak-capacity-planning-dashboard.json | 負荷推定、ログインフロー分析 |
ダウンロード: github.com/keycloak/keycloak-grafana-dashboard
14. まとめ
SSO構築を成功させるための要点をまとめる。
- プロトコル: OIDCをデフォルトとし、レガシーエンタープライズアプリにのみSAMLを適用
- 認可体系: RBACで基本構造を構築し、きめ細かな制御が必要な場合はABACを組み合わせる
- 高可用性: 最低3ノード + Infinispan分散キャッシュ + 共有DB構成
- アプリケーション連携: Spring BootはResource Server JWT検証、SPAはkeycloak-js + PKCE、Next.jsはAuth.jsを活用
- モニタリング: Prometheusメトリクス + Grafanaダッシュボードでログイン失敗率、トークン発行量などを追跡
Keycloakは、これらすべてをオープンソースで提供する次世代デジタルアイデンティティガバナンスの中核である。
References
- Keycloak Official Documentation. https://www.keycloak.org/guides
- Keycloak Production Configuration. https://www.keycloak.org/server/configuration-production
- Keycloak Operator Deployment. https://www.keycloak.org/operator/basic-deployment
- Keycloak OIDC Integration. https://www.keycloak.org/securing-apps/oidc-layers
- Keycloak JavaScript Adapter. https://www.keycloak.org/securing-apps/javascript-adapter
- Keycloak Node.js Adapter. https://www.keycloak.org/securing-apps/nodejs-adapter
- Keycloak Authorization Services. https://www.keycloak.org/docs/latest/authorization_services/
- Keycloak Admin REST API. https://www.keycloak.org/docs-api/latest/rest-api/index.html
- Keycloak Distributed Caching. https://www.keycloak.org/server/caching
- Keycloak High Availability. https://www.keycloak.org/high-availability/introduction
- Keycloak Metrics & Health. https://www.keycloak.org/observability/configuration-metrics
- Keycloak Grafana Dashboards. https://github.com/keycloak/keycloak-grafana-dashboard
- Auth.js Keycloak Provider. https://authjs.dev/getting-started/providers/keycloak
- OAuth 2.0 RFC 6749. https://datatracker.ietf.org/doc/html/rfc6749
- OpenID Connect Core 1.0. https://openid.net/specs/openid-connect-core-1_0.html