Skip to content
Published on

次世代デジタルアイデンティティとSSO、そしてKeycloak実務完全ガイド

Authors
  • Name
    Twitter

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.0OAuth 2.0OIDC
目的認証(Authentication)認可(Authorization)認証 + 認可
データ形式XML (Assertion)JSONJSON (JWT)
トークンSAML AssertionAccess TokenID 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: 独立したテナント空間。master Realmは管理用であり、実際のサービスには別途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-basedRealm/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 LoginGoogle, GitHub, Facebook, Microsoft, LinkedIn, GitLab等
プロトコルベースOIDC v1.0, OAuth 2.0, SAML 2.0

8.2 Google IdP設定例

  1. Google Cloud ConsoleでOAuth 2.0認証情報を作成
  2. Redirect URI: https://auth.example.com/realms/{realm}/broker/google/endpoint
  3. Keycloak Admin Console、Identity Providers、Googleを追加
  4. Client IDとClient Secretを入力

8.3 LDAP/Active Directory連携

User Federationを通じてLDAPと連携できる。

設定Active Directory一般LDAP
Connection URLldaps://ad-server:636ldap://ldap-server:389
Users DNOU=Users,DC=corp,DC=example,DC=comou=People,dc=example,dc=com
Username AttributesAMAccountNameuid
UUID AttributeobjectGUIDentryUUID

同期モード:

  • 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.jsonSLIおよび診断分析
keycloak-capacity-planning-dashboard.json負荷推定、ログインフロー分析

ダウンロード: github.com/keycloak/keycloak-grafana-dashboard


14. まとめ

SSO構築を成功させるための要点をまとめる。

  1. プロトコル: OIDCをデフォルトとし、レガシーエンタープライズアプリにのみSAMLを適用
  2. 認可体系: RBACで基本構造を構築し、きめ細かな制御が必要な場合はABACを組み合わせる
  3. 高可用性: 最低3ノード + Infinispan分散キャッシュ + 共有DB構成
  4. アプリケーション連携: Spring BootはResource Server JWT検証、SPAはkeycloak-js + PKCE、Next.jsはAuth.jsを活用
  5. モニタリング: Prometheusメトリクス + Grafanaダッシュボードでログイン失敗率、トークン発行量などを追跡

Keycloakは、これらすべてをオープンソースで提供する次世代デジタルアイデンティティガバナンスの中核である。


References