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>
This commit is contained in:
427
KEYCLOAK_SETUP.md
Normal file
427
KEYCLOAK_SETUP.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# 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 scopes** → **hr-portal-dedicated** → **Mappers**
|
||||
|
||||
#### 4.1 確認現有 Mappers
|
||||
|
||||
應該已有:
|
||||
- `aud claim` - Audience mapping
|
||||
- `Client IP Address` - Client IP
|
||||
- `Client ID` - Client identifier
|
||||
- `Client 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 內建測試工具
|
||||
|
||||
1. 進入 `hr-portal` Client → **Client scopes** → **Evaluate**
|
||||
2. 選擇測試用戶 (例如: testuser)
|
||||
3. 點擊 **Generated access token**
|
||||
4. 檢查 token payload 是否包含所需 claims
|
||||
|
||||
#### 5.2 測試 OIDC Discovery
|
||||
|
||||
在瀏覽器或命令列執行:
|
||||
```bash
|
||||
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 執行:
|
||||
```javascript
|
||||
// 獲取 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 → **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:
|
||||
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
|
||||
```
|
||||
|
||||
3. 點擊 **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 後端創建對應的員工記錄:
|
||||
|
||||
```bash
|
||||
# 使用 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 的 `REGISTER` 和 `UPDATE_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 是否啟用
|
||||
|
||||
---
|
||||
|
||||
## 📚 參考資料
|
||||
|
||||
- [Keycloak Documentation](https://www.keycloak.org/documentation)
|
||||
- [OIDC Specification](https://openid.net/specs/openid-connect-core-1_0.html)
|
||||
- [Keycloak Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/index.html)
|
||||
Reference in New Issue
Block a user