Files
hr-portal/KEYCLOAK_SETUP.md
Porsche Chen 360533393f feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage
Major Features:
-  Multi-tenant architecture (tenant isolation)
-  Employee CRUD with lifecycle management (onboarding/offboarding)
-  Department tree structure with email domain management
-  Company info management (single-record editing)
-  System functions CRUD (permission management)
-  Email account management (multi-account per employee)
-  Keycloak SSO integration (auth.lab.taipei)
-  Redis session storage (10.1.0.254:6379)
  - Solves Cookie 4KB limitation
  - Cross-system session sharing
  - Sliding expiration (8 hours)
  - Automatic token refresh

Technical Stack:
Backend:
- FastAPI + SQLAlchemy
- PostgreSQL 16 (10.1.0.20:5433)
- Keycloak Admin API integration
- Docker Mailserver integration (SSH)
- Alembic migrations

Frontend:
- Next.js 14 (App Router)
- NextAuth 4 with Keycloak Provider
- Redis session storage (ioredis)
- Tailwind CSS

Infrastructure:
- Redis 7 (10.1.0.254:6379) - Session + Cache
- Keycloak 26.1.0 (auth.lab.taipei)
- Docker Mailserver (10.1.0.254)

Architecture Highlights:
- Session管理由 Keycloak + Redis 統一控制
- 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session
- Token 自動刷新,異質服務整合
- 未來可無縫遷移到雲端

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-23 20:12:43 +08:00

9.1 KiB

Keycloak 配置指南 - HR Portal

📋 前置需求

  • Keycloak 已運行在 https://auth.ease.taipei
  • Realm porscheworld 已創建
  • 管理員帳號可登入

🔧 配置步驟

1. 創建 HR Portal 客戶端

1.1 登入 Keycloak Admin Console

  1. 訪問: https://auth.ease.taipei
  2. 點擊「Administration Console」
  3. 使用管理員帳號登入

1.2 選擇 Realm

  1. 左上角選擇 porscheworld realm
  2. 如果沒有,請先創建

1.3 創建新 Client

  1. 左側選單 → Clients
  2. 點擊 Create client 按鈕

General Settings:

Client type: OpenID Connect
Client ID: hr-portal
Name: HR Portal
Description: HR Management Portal

點擊 Next

Capability config:

Client authentication: OFF (公開客戶端)
Authorization: OFF
Authentication flow:
  ✓ Standard flow (Authorization Code Flow)
  ✓ Direct access grants

點擊 Next

Login settings:

Root URL: https://hr.ease.taipei
Home URL: https://hr.ease.taipei
Valid redirect URIs:
  https://hr.ease.taipei/*
  http://localhost:3000/*  (開發用)
Valid post logout redirect URIs:
  https://hr.ease.taipei/*
  http://localhost:3000/*
Web origins:
  https://hr.ease.taipei
  http://localhost:3000

點擊 Save


2. 配置 Client 進階設定

進入 Client hr-portal 的設定頁面:

2.1 Settings 標籤

Access settings:

Root URL: https://hr.ease.taipei
Home URL: https://hr.ease.taipei
Valid redirect URIs: https://hr.ease.taipei/*
Valid post logout redirect URIs: https://hr.ease.taipei/*
Web origins: https://hr.ease.taipei
Admin URL: (留空)

Login settings:

Login theme: keycloak (或自訂主題)
Consent required: OFF
Display client on screen: ON

Logout settings:

Front channel logout: OFF
Backchannel logout URL: (留空)
Backchannel logout session required: ON

點擊 Save

2.2 Advanced 標籤

Advanced settings:

Access Token Lifespan: 5 minutes (300 秒)
Client Session Idle: 30 minutes (1800 秒)
Client Session Max: 12 hours (43200 秒)

Authentication flow overrides:

Browser Flow: browser (預設)
Direct Grant Flow: direct grant (預設)

點擊 Save


3. 配置 Client Scopes

3.1 查看預設 Scopes

進入 hr-portal Client → Client scopes 標籤

Assigned default client scopes 應包含:

  • email
  • profile
  • roles
  • web-origins

3.2 建議新增的 Scopes (可選)

如需更細緻的權限控制,可創建自訂 scope:

  1. 左側選單 → Client scopes
  2. 點擊 Create client scope

範例: hr-admin scope

Name: hr-admin
Description: HR Portal Admin Access
Protocol: openid-connect
Display on consent screen: ON
Include in token scope: ON

創建後,回到 hr-portal Client → Client scopes 標籤 → 將 hr-admin 加入 Assigned optional client scopes


4. 配置用戶屬性映射 (Mappers)

進入 hr-portal Client → Client scopeshr-portal-dedicatedMappers

4.1 確認現有 Mappers

應該已有:

  • aud claim - Audience mapping
  • Client IP Address - Client IP
  • Client ID - Client identifier
  • Client Host - Client hostname

4.2 新增自訂 Mapper (用於員工資訊)

點擊 Add mapperBy configurationUser Attribute

Mapper 1: Employee ID

Name: employee_id
User Attribute: employee_id
Token Claim Name: employee_id
Claim JSON Type: String
Add to ID token: ON
Add to access token: ON
Add to userinfo: ON
Multivalued: OFF
Aggregate attribute values: OFF

Mapper 2: Business Unit

Name: business_unit
User Attribute: business_unit_id
Token Claim Name: business_unit_id
Claim JSON Type: String
Add to ID token: ON
Add to access token: ON
Add to userinfo: ON

點擊 Save


5. 測試 Client 配置

5.1 使用 Keycloak 內建測試工具

  1. 進入 hr-portal Client → Client scopesEvaluate
  2. 選擇測試用戶 (例如: testuser)
  3. 點擊 Generated access token
  4. 檢查 token payload 是否包含所需 claims

5.2 測試 OIDC Discovery

在瀏覽器或命令列執行:

curl https://auth.ease.taipei/realms/porscheworld/.well-known/openid-configuration

應返回完整的 OIDC 配置,包含:

  • issuer
  • authorization_endpoint
  • token_endpoint
  • userinfo_endpoint
  • end_session_endpoint

🧪 測試登入流程

測試 1: 前端登入測試

  1. 訪問 https://hr.ease.taipei (或 http://localhost:3000)
  2. 系統自動重定向到 Keycloak 登入頁面
  3. 輸入測試用戶帳號密碼
  4. 登入成功後重定向回 HR Portal
  5. 檢查 localStorage 中是否有 kc-token

測試 2: Token 驗證

在瀏覽器 Console 執行:

// 獲取 token
const keycloak = window.keycloak;
console.log('Access Token:', keycloak.token);
console.log('ID Token:', keycloak.idToken);
console.log('Refresh Token:', keycloak.refreshToken);

// 解析 token
const payload = JSON.parse(atob(keycloak.token.split('.')[1]));
console.log('Token Payload:', payload);

檢查 payload 應包含:

  • sub: User ID
  • email: 用戶 email
  • preferred_username: 用戶名
  • given_name, family_name: 姓名
  • employee_id, business_unit_id: 自訂屬性 (如已配置)

🔐 安全設定建議

1. Token 生命週期

建議設定:

Access Token Lifespan: 5 minutes
SSO Session Idle: 30 minutes
SSO Session Max: 12 hours
Refresh Token Max Reuse: 0 (單次使用)

2. 密碼策略

Realm Settings → AuthenticationPassword Policy

建議啟用:

  • Minimum Length: 8
  • Not Username
  • Uppercase Characters: 1
  • Lowercase Characters: 1
  • Digits: 1
  • Special Characters: 1
  • Not Recently Used: 3

3. 啟用 MFA (多因素認證)

Realm Settings → AuthenticationFlows

編輯 Browser flow:

  1. 找到 Browser - Conditional OTP
  2. Requirement 改為 Required
  3. 點擊 Save

用戶首次登入時會要求設定 OTP (Google Authenticator / FreeOTP)


👥 測試用戶管理

創建測試用戶

  1. 左側選單 → Users
  2. 點擊 Add user

User details:

Username: test.employee
Email: test.employee@porscheworld.tw
First name: Test
Last name: Employee
Email verified: ON
Enabled: ON
  1. 點擊 Create

設定密碼:

  1. 進入用戶 → Credentials 標籤
  2. 點擊 Set password
  3. 輸入密碼 (例如: Test1234!)
  4. Temporary: OFF
  5. 點擊 Save

設定屬性 (如需要):

  1. 進入用戶 → Attributes 標籤
  2. 新增屬性:
    • Key: employee_id, Value: EMP001
    • Key: business_unit_id, Value: 1
  3. 點擊 Save

🔄 同步到後端 HR 系統

選項 1: 手動同步

當在 Keycloak 創建用戶後,需要在 HR Portal 後端創建對應的員工記錄:

# 使用 API 創建員工
curl -X POST https://hr-api.ease.taipei/api/v1/employees/ \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "employee_id": "EMP001",
    "username": "test.employee",
    "keycloak_user_id": "KEYCLOAK_USER_UUID",
    "first_name": "Test",
    "last_name": "Employee",
    "chinese_name": "測試員工",
    "email": "test.employee@porscheworld.tw",
    "business_unit_id": 1,
    "position": "Test Engineer",
    "job_level": "Staff",
    "hire_date": "2024-01-01",
    "status": "active"
  }'

選項 2: 自動同步 (使用 Keycloak Event Listener)

在後端實現 Keycloak Event Listener (進階功能):

  1. 監聽 Keycloak 的 REGISTERUPDATE_PROFILE 事件
  2. 當用戶註冊或更新資料時,自動在 HR 資料庫創建/更新記錄
  3. 使用 Keycloak Admin API 獲取用戶資訊

📝 配置檢查清單

部署前確認:

  • Client hr-portal 已創建
  • Client Type: OpenID Connect
  • Client authentication: OFF (公開客戶端)
  • Standard flow: ✓ Enabled
  • Valid redirect URIs: https://hr.ease.taipei/*
  • Web origins: https://hr.ease.taipei
  • Access token lifespan: 5 minutes
  • 測試用戶已創建並可登入
  • Token 包含必要的 claims
  • 前端可成功登入並獲取 token
  • 前端可使用 token 調用後端 API

🐛 常見問題

問題 1: Invalid redirect URI

原因: 前端重定向 URI 不在 Valid redirect URIs 列表中

解決:

  1. 檢查前端配置的 VITE_KEYCLOAK_URL
  2. 確認 Keycloak Client 的 Valid redirect URIs 包含前端 URL

問題 2: CORS 錯誤

原因: Web origins 未正確配置

解決: 在 Keycloak Client 設定中,確保 Web origins 包含前端域名:

https://hr.ease.taipei

問題 3: Token 過期過快

原因: Access token lifespan 設定過短

解決: 調整 Client → Advanced → Access Token Lifespan 建議: 5-15 分鐘

問題 4: 用戶無法登入

檢查:

  1. 用戶是否已啟用 (Enabled: ON)
  2. Email 是否已驗證 (Email verified: ON)
  3. 密碼是否正確設定
  4. Client 的 Authentication flow 是否啟用

📚 參考資料