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>
9.1 KiB
Keycloak 配置指南 - HR Portal
📋 前置需求
- Keycloak 已運行在
https://auth.ease.taipei - Realm
porscheworld已創建 - 管理員帳號可登入
🔧 配置步驟
1. 創建 HR Portal 客戶端
1.1 登入 Keycloak Admin Console
- 訪問:
https://auth.ease.taipei - 點擊「Administration Console」
- 使用管理員帳號登入
1.2 選擇 Realm
- 左上角選擇
porscheworldrealm - 如果沒有,請先創建
1.3 創建新 Client
- 左側選單 → Clients
- 點擊 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 應包含:
emailprofilerolesweb-origins
3.2 建議新增的 Scopes (可選)
如需更細緻的權限控制,可創建自訂 scope:
- 左側選單 → Client scopes
- 點擊 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 scopes → hr-portal-dedicated → Mappers
4.1 確認現有 Mappers
應該已有:
aud claim- Audience mappingClient IP Address- Client IPClient ID- Client identifierClient Host- Client hostname
4.2 新增自訂 Mapper (用於員工資訊)
點擊 Add mapper → By configuration → User 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 內建測試工具
- 進入
hr-portalClient → Client scopes → Evaluate - 選擇測試用戶 (例如: testuser)
- 點擊 Generated access token
- 檢查 token payload 是否包含所需 claims
5.2 測試 OIDC Discovery
在瀏覽器或命令列執行:
curl https://auth.ease.taipei/realms/porscheworld/.well-known/openid-configuration
應返回完整的 OIDC 配置,包含:
issuerauthorization_endpointtoken_endpointuserinfo_endpointend_session_endpoint
🧪 測試登入流程
測試 1: 前端登入測試
- 訪問
https://hr.ease.taipei(或http://localhost:3000) - 系統自動重定向到 Keycloak 登入頁面
- 輸入測試用戶帳號密碼
- 登入成功後重定向回 HR Portal
- 檢查 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 IDemail: 用戶 emailpreferred_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 → Authentication → Password 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 → Authentication → Flows
編輯 Browser flow:
- 找到
Browser - Conditional OTP - 將 Requirement 改為 Required
- 點擊 Save
用戶首次登入時會要求設定 OTP (Google Authenticator / FreeOTP)
👥 測試用戶管理
創建測試用戶
- 左側選單 → Users
- 點擊 Add user
User details:
Username: test.employee
Email: test.employee@porscheworld.tw
First name: Test
Last name: Employee
Email verified: ON
Enabled: ON
- 點擊 Create
設定密碼:
- 進入用戶 → Credentials 標籤
- 點擊 Set password
- 輸入密碼 (例如: Test1234!)
- Temporary: OFF
- 點擊 Save
設定屬性 (如需要):
- 進入用戶 → Attributes 標籤
- 新增屬性:
- Key:
employee_id, Value:EMP001 - Key:
business_unit_id, Value:1
- Key:
- 點擊 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 (進階功能):
- 監聽 Keycloak 的
REGISTER和UPDATE_PROFILE事件 - 當用戶註冊或更新資料時,自動在 HR 資料庫創建/更新記錄
- 使用 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 列表中
解決:
- 檢查前端配置的
VITE_KEYCLOAK_URL - 確認 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: 用戶無法登入
檢查:
- 用戶是否已啟用 (Enabled: ON)
- Email 是否已驗證 (Email verified: ON)
- 密碼是否正確設定
- Client 的 Authentication flow 是否啟用