# 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)