LDAP 設定(LDAP Configuration)
事實基準:2026-07-05 從 FE
src/views/ldap-config/LdapConfigForm.vue+ BEapi/system_config//api/user_auth_provider/+ jedi-system-config / jedi-login 套件 + routes.json / db_schema.json 掃出。
1. 功能描述
LDAP 設定頁是第三方登入(LDAP / Active
Directory)的連線參數維護頁:管理員在此設定企業 LDAP 或 AD
伺服器資訊,設定值同時是登入流程(app/auth/service/login_service.py::init_config())在使用者選擇
LDAP
登入時實際套用的參數來源——不是獨立於登入之外的靜態設定。頁面只有單一表單,無列表、無分頁。
主要使用者:系統管理員(頁面本身無角色守門,見 §3)。
角色速覽:
| 角色 | 一句話 |
|---|---|
| 系統管理員(可見本選單者) | 維護 LDAP/AD 連線設定、測試連線 |
| 一般使用者 | 無此選單(ui_routes 可見性未命中);但可直接打 API(見
§3 ⚠️) |
1.1 功能總覽(本頁全部功能)
| # | 功能 | 說明 | 位置 | 詳述 |
|---|---|---|---|---|
| 1 | 載入既有設定 | 進頁自動 GET 目前的 LDAP 設定回填表單 | 整頁(onMounted) |
§6.1 |
| 2 | 伺服器欄位 | 必填,LDAP/AD 伺服器 IP 或 FQDN | 表單 | §6.1 |
| 3 | 埠號欄位 | 必填,數字輸入,預設 389 |
表單 | §6.1 |
| 4 | Base DN 欄位 | 必填,LDAP 查詢起點 DN | 表單 | §6.1 |
| 5 | LDAP 類型下拉 | 二選一:Microsoft AD / LDAP(即
OpenLDAP),對應 is_ad 布林 |
表單 | §9.3 |
| 6 | 加密方式下拉 | 三選一:無 / SSL / STARTTLS |
表單 | §9.3 |
| 7 | 認證帳號欄位 | 選填,LDAP bind 用的服務帳號 | 表單 | §6.1 |
| 8 | 密碼欄位 | 選填,input type="password";留白送出=沿用舊密碼(見
§6.1 changePwd) |
表單 | §6.1 |
| 9 | 連線測試 | 用表單目前值即時測試 LDAP/AD bind,不落地儲存 | 表單底部按鈕 | UC-LDAP-02 |
| 10 | 儲存設定 | 寫回 system_configs(THIRD_PARTY_LOGIN/LDAP) |
表單底部按鈕 | UC-LDAP-01 |
| 11 | 離開頁面確認 | 表單有異動時離開觸發確認
dialog(onBeforeRouteLeave) |
全域 | — |
UC(§2)展開 1(載入)已併入儲存流程圖;核心路徑為 UC-LDAP-01(儲存)與 UC-LDAP-02(連線測試),兩者共用同一張流程圖。
2. Use Case
角色速覽:
| 角色 | 一句話 |
|---|---|
| 系統管理員(可見本選單者) | 維護 LDAP/AD 連線設定、測試連線 |
| 一般使用者 | 無此選單(ui_routes 可見性未命中);但可直接打 API(見
§3 ⚠️) |
視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點。
UC-LDAP-01 儲存 LDAP 設定
| 項目 | 內容 |
|---|---|
| 角色 | 系統管理員(BE 僅檢查登入,見 §3) |
| 前置條件 | 已登入;server / port /
base_dn 已填 |
| 產出 / 後置條件 | public.system_configs(group=THIRD_PARTY_LOGIN,
key=LDAP)新增或更新一列;密碼欄未勾異動則保留舊值 |
UC-LDAP-02 連線測試
| 項目 | 內容 |
|---|---|
| 角色 | 系統管理員(BE 僅檢查登入) |
| 前置條件 | 已登入;表單必填欄位已填(不需先儲存) |
| 產出 / 後置條件 | 無資料異動,純測試 bind;成功回 200、失敗回
401(LOGIN_401014) |
3. 權限矩陣
| 操作 | FE 判定 | BE 強制 | BE 檢查位置 |
|---|---|---|---|
| 進入本頁 | ui_routes web-menu 可見性 |
無端點層強制(僅 @jwt_required) |
api/system_config/routes/system_config_route.py:42,109 |
| 讀取設定 | 有選單即可 | ⚠️ 僅 @jwt_required,無角色 /
能力守門 |
SystemConfigGroupRoute.get(system_config_route.py:96-104) |
| 儲存設定 | 有選單即可 | ⚠️ 僅 @jwt_required,無角色 /
能力守門 |
SystemConfigGroupRoute.put(system_config_route.py:106-119) |
| 連線測試 | 有選單即可 | ⚠️ 僅 @jwt_required,無角色 /
能力守門 |
LdapConnectTestRoute.post(api/user_auth_provider/routes/ldap_route.py:12-24) |
⚠️ 本頁三個端點全部僅
@jwt_required(),無任何角色 / 平台管理員守門——任何已登入使用者(不限系統管理員)直接打 API 都能讀取 / 覆寫全系統 LDAP 連線設定(含觸發連線測試)。與 _overview §5 一致(本群多頁通病)。本 spec 只記錄不修,收進 §12。
4. 狀態機與前置條件
本頁無狀態機,僅表單前端驗證與後端密碼保留邏輯:
| 條件 | 效果 | 出處 |
|---|---|---|
server / port / base_dn
任一為空 |
前端阻擋送出(連線測試與儲存皆同一驗證),focus 錯誤欄位 | LdapConfigForm.vue:30-42, 95-107, 128-139 |
儲存時 changePwd=false(密碼欄未手動改過) |
BE 用資料庫既有 secret 覆蓋
payload,不寫入前端傳來的空值 |
jedi-system-config
system_config_domain_service.py:118-132(update_config_by_group_key) |
連線測試時 payload 無 secret 但有
user |
BE 沿用資料庫既有 secret
做測試(避免每次測試都要重打密碼) |
app/user_auth_provider/service/ldap_service.py:32-45(init_config) |
| 設定不存在(首次進頁) | BE get_system_config_by_key 找不到會 raise
NotFound;FE getConfig() 未特別 catch(見 §12
坑 1) |
system_config_service.py:31-39 |
5. UI 設計
📸 實機截圖待補(亮色模式,另有專責 session)。目前以版面骨架圖示意。待補清單:初次進頁空表單狀態、已有設定回填狀態、連線測試成功 / 失敗 toast、必填欄位驗證錯誤紅框狀態。
版面骨架(單卡片表單,無 tab、無列表):
狀態呈現:loading 期間顯示
LoadingState(page size)取代表單;密碼欄為原生
<input type="password">(非 PrimeVue
Password 元件),change 時同時標記 hasChange
與 passwordHasChange;儲存 / 測試按鈕在送出期間顯示
loading spinner(submitted state)。無
socket、無多人協作提示。
6. API 規格
Envelope:成功 {"code": 1, "data": {...}}、失敗
{"code": 0, "msg": "...", "data": {...}}(見 CLAUDE.md
Response Format)。
6.1 Endpoint 總清單(本頁呼叫的全部 endpoint)
| 分類 | Method + Path | 說明 | 詳述 |
|---|---|---|---|
| 讀取 | GET /api/1.0/system/config/<group>/<key> |
讀取 LDAP 設定(group=THIRD_PARTY_LOGIN,
key=LDAP),回應剔除 secret |
§6.2 |
| 儲存 | PUT /api/1.0/system/config/<group>/<key> |
更新 LDAP 設定(不存在則新增) | §6.2 |
| 連線測試 | POST /api/1.0/ldap/connect-test |
用送入的參數即時測試 LDAP/AD bind,不落地 | §6.3 |
endpoint 存在性對
routes.json核實(system-config.systemconfiggrouproute/user_auth_provider.ldapconnecttestroute)。group/key常數值取自 FE 呼叫點LdapConfigForm.vue:86, 115(API.SYSTEM_CONFIG + '/THIRD_PARTY_LOGIN/LDAP'),非泛用/system/config全量端點。
6.2 讀取 /
儲存(system_config/<group>/<key>)
GET Response
data(SystemConfigResponse,api/system_config/serializers/system_config.py:9-15):
| 欄位 | 型別 | 說明 |
|---|---|---|
| uid | string | 該設定列的 UUID |
| group | string | 固定 THIRD_PARTY_LOGIN |
| key | string | 固定 LDAP |
| value | dict | LDAP 參數本體(見下表),回應前若含 secret
一律剔除(system_config_route.py:18,47-49
HIDDEN_SECRET 清單含 THIRD_PARTY_LOGIN) |
| created_at / updated_at | datetime | %Y-%m-%d %H:%M:%S |
value 內部欄位(對照
LdapConfigRequest,api/user_auth_provider/serializers/ldap_config.py:4-11,及
FE currData 初值
LdapConfigForm.vue:23-29):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| server | string | 是 | LDAP/AD 伺服器 |
| port | int | 是 | 埠號,FE 預設 389 |
| base_dn | string | 是 | Base DN |
| is_ad | bool | 否 | true=Microsoft AD(NTLM
認證)、false=OpenLDAP |
| encryption | string | 否 | '' / SSL / STARTTLS |
| user | string | 否 | bind 用認證帳號 |
| secret | string | 否 | bind 密碼(GET 回應永遠不含此欄;只在 PUT/POST 送入) |
PUT Request:同上 value 欄位 + 頂層
changePwd(bool,FE 送 passwordHasChange
的值,LdapConfigForm.vue:114)——changePwd=false
時 BE 用既有 secret 覆蓋,changePwd=true
才採用送入的新密碼。出處:jedi-system-config
system_config_domain_service.py:118-132(update_config_by_group_key)。
BE
鏈路:SystemConfigGroupRoute.get/put(api/system_config/routes/system_config_route.py:92-119)→
SystemConfigService.get_system_config_by_key /
update_config_by_group_key(jedi-system-config
app/service/system_config_service.py:32-39,142-149)→
SystemConfigDomainService.update_config_by_group_key(同套件
domain/service/system_config_domain_service.py:118-132,密碼保留邏輯
+ 找不到自動新增)→ repo 寫 public.system_configs。
6.3
連線測試(POST /ldap/connect-test)
Request(LdapConfigRequest,同 §6.2
value 欄位形狀,無 changePwd):
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
| server | string | 是 | LDAP/AD 伺服器 |
| port | int | 是 | 埠號 |
| base_dn | string | 是 | Base DN |
| is_ad | bool | 否 | true=Microsoft AD(NTLM
認證)、false=OpenLDAP |
| encryption | string | 否 | '' / SSL / STARTTLS |
| user | string | 否 | bind 用認證帳號 |
| secret | string | 否 | bind 密碼 |
Response:BooleanResponse(成功恆
true;失敗直接拋例外變成非 2xx,見下)。
BE
鏈路:LdapConnectTestRoute.post(api/user_auth_provider/routes/ldap_route.py:12-24)→
LdapService.test_connect(app/user_auth_provider/service/ldap_service.py:21-30):先
init_config() 組出 LoginConfigDTO(若送入
payload 無 secret 但有 user,沿用資料庫既有
secret,ldap_service.py:38-45)→ jedi-login
get_authenticate_adapter(LDAP, config) →
LdapAdapter.test_connect()(~/Projects/Jedicogy/module/jedi-python-package/jedi-login/jedi_login/infra/adapter/authenticate_adapter/ldap/ldap_adapter.py:81-91):建立
ldap3.Server(依 encryption 決定
ldaps:// 前綴 / use_ssl)→
Connection.bind()(AD 用 NTLM、STARTTLS
額外呼叫 start_tls())→ bind 成功回
True,失敗(含逾時、憑證錯、伺服器不可達)一律包成
UnauthorizedError(LOGIN_401014) → HTTP 401。
已知例外碼(jedi-login
common/enum/error_code.py):LOGIN_404006(LDAP_SERVER_NOT_CONFIGURED,server
空字串)、LOGIN_405003(LDAP_ACCOUNT_FORMAT_ERROR,AD
帳號非 DOMAIN\username
格式)、LOGIN_401014(LDAP_CONNECT_FAILED,test_connect()
統一捕捉的失敗碼)。error code 前綴為 LOGIN_(jedi-login
套件自有前綴,非主專案
GRC_/AUTH_,查證方式:grep
~/Projects/Jedicogy/module/jedi-python-package/jedi-login/jedi_login/common/enum/error_code.py,非
common/code/*_error_code.py——本設定的錯誤碼定義不在主專案倉庫內)。
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 | 角色 |
|---|---|
src/views/ldap-config/LdapConfigForm.vue |
唯一檢視:表單載入 / 驗證 / 儲存 / 連線測試 / 離開確認 |
src/config/router/index.js:317-328 |
route
ldap-config(/system/ldap-config) |
src/config/api/api.js:328,339 |
SYSTEM_CONFIG / LDAP_CONNECT_TEST
端點常數 |
src/service/BaseService.js |
HTTP 包裝(get/put/post) |
src/config/locales/i18n/zh-tw/pages.json:379-406 |
ldap_config 命名空間(欄位標籤 / placeholder /
驗證訊息) |
8. 後端檔案地圖(本頁核心鏈路)
| 鏈路 | Route | App Service | 底層 |
|---|---|---|---|
| 讀取 / 儲存設定 | api/system_config/routes/system_config_route.py::SystemConfigGroupRoute |
jedi-system-config
SystemConfigService.get_system_config_by_key /
update_config_by_group_key |
jedi-system-config SystemConfigDomainService →
SystemConfigRepoImpl →
public.system_configs |
| 連線測試 | api/user_auth_provider/routes/ldap_route.py::LdapConnectTestRoute |
app/user_auth_provider/service/ldap_service.py::LdapService.test_connect |
jedi-login get_authenticate_adapter →
LdapAdapter(ldap3 套件) |
| 登入時實際套用 | (非本頁 route,登入頁觸發) | app/auth/service/login_service.py::LoginService.init_config(讀同一筆
THIRD_PARTY_LOGIN/LDAP 設定) |
jedi-login LoginService.process_login →
LdapAdapter.authenticate |
DDD
提醒:路由層(SystemConfigGroupRoute /
LdapConnectTestRoute)皆不碰 DB,委派 app service /
jedi-system-config
service,符合分層規範。但三端點皆無角色守門(僅
@jwt_required)——新增本頁 API 或提升守門強度時,比照 §3
現況先跟 user 確認是否要補角色檢查,勿在不知情下複製「僅
JWT」寫法到有敏感度更高的設定頁。
9. DB
9.1 資料表總清單(本頁讀寫的全部表)
| 表 | 讀/寫 | 說明 | 詳述 |
|---|---|---|---|
public.system_configs |
讀 / 寫 | LDAP 設定實際儲存位置:group='THIRD_PARTY_LOGIN',
key='LDAP', value 為 jsonb(欄位見 §6.2) |
§9.3 |
public.user_auth_providers |
讀(間接,登入時) | 使用者 ↔︎ LDAP
第三方登入綁定關係(provider='LDAP');本頁不直接讀寫此表,但本頁設定的伺服器參數是登入時解析此表
provider_user_id 的前提 |
§9.3 |
表結構 / 欄位 / comment 取自
scripts/deliverables/out/db_schema.json(2026-07-05 dump)。
9.2 ER 圖
本頁只涉及單表寫入(system_configs 為通用 key-value
表,無專屬 FK),關聯示意見 _overview §6
核心資料模型圖(system_configs
節點)。user_auth_providers 與 LDAP
設定為邏輯關聯(同一套登入流程共用參數),非資料庫層 FK 關聯,故不另畫
ER。
9.3 核心表欄位卡
public.system_configs(comment:「系統配置表:儲存系統設定參數,以群組和鍵值對方式組織」):
| 欄位 | 型別 | 說明 |
|---|---|---|
| id | integer | 主鍵 |
| uid | varchar(50) | 配置唯一識別碼 UUID |
| group | varchar(255) | 配置群組(本頁固定 THIRD_PARTY_LOGIN) |
| key | varchar(255) | 配置鍵名(本頁固定 LDAP) |
| value | jsonb | 配置值(本頁形狀見 §6.2,含
server/port/base_dn/is_ad/encryption/user/secret) |
| tenant_id | integer|null | 設定歸屬租戶(本頁未見 FE 傳入,實務為全域設定) |
| org_unit_id | integer|null | 設定歸屬部門(同上,本頁未使用) |
| created_at / updated_at | timestamp | 審計時間戳(表本身無
created_user/updated_user 欄位) |
public.user_auth_providers(comment:「使用者第三方登入驗證表:記錄使用者與第三方身份提供者的綁定關係」,本頁不寫,僅背景關聯說明):uid
/ user_id(FK→users.id CASCADE)/
provider(如 LDAP)/
provider_user_id(LDAP entryUUID 或 AD objectGUID)/
created_at / updated_at /
created_user / updated_user。
10. 頁面邏輯與資料對應
載入時序:onMounted →
getConfig() →
GET /system/config/THIRD_PARTY_LOGIN/LDAP → 成功則
currData.value = res.value(secret 因 BE
剔除而為 undefined,表單密碼欄顯示空白)→
loading=false。
欄位對應(畫面 ↔︎ FE state ↔︎ API 欄位):
| 畫面元素 | FE state | API 欄位 |
|---|---|---|
| 伺服器 | currData.server |
value.server |
| 埠號 | currData.port |
value.port |
| Base DN | currData.base_dn |
value.base_dn |
| LDAP 類型下拉 | currData.is_ad |
value.is_ad |
| 加密方式下拉 | currData.encryption |
value.encryption |
| 認證帳號 | currData.user |
value.user |
| 密碼 | currData.secret + passwordHasChange |
value.secret + 頂層 changePwd |
儲存流程:onSaveClick() →
v$.value.$touch() 驗證必填三欄 → 通過則
update():組
payload = {...currData.value, changePwd: passwordHasChange.value}
→ PUT /system/config/THIRD_PARTY_LOGIN/LDAP → 成功後重新
getConfig() 回填(不是直接用 response
蓋,而是整個重抓)、showSuccess、重置
hasChange / passwordHasChange。
連線測試流程:onTestConnectClick() →
同樣先驗證必填三欄 → POST /ldap/connect-test,body
為當前表單值(非已儲存值,允許先測後存)→ 成功
showSuccess('連線成功');失敗(.finally 只重置
submitted,無專屬 .catch,錯誤走全域攔截器
toast,見 §12 坑 2)。
錯誤對應:全域 axios
攔截器(BaseService)catch 非 2xx 轉
toast;本頁未見自訂錯誤碼對應文案,因此 LOGIN_401014
等碼會顯示 BE 給的通用 msg(中文「LDAP連線失敗」),非 i18n
key 對照。
11. 背景行為與外部依賴
| 類型 | 內容 |
|---|---|
| 通知 | 無 |
| Job / 排程 | 無 |
| Socket | 無:純表單 CRUD,資料更新靠操作後重新 GET |
| jedi-* 套件 | jedi-system-config(system_configs 表
CRUD +
密碼保留邏輯)、jedi-login(LdapAdapter
實際 bind 測試 / 登入時驗證,ldap3
第三方套件)、jedi-auth(JWT) |
| 系統參數 | 本頁本身即系統參數維護頁;設定值被
app/auth/service/login_service.py::LoginService.init_config()
在使用者選擇 LDAP 登入方式時讀取套用(非本頁 route,跨頁依賴) |
12. 邊界情況與已知坑
- 三端點皆無角色守門:讀取 / 儲存 / 連線測試僅
@jwt_required(),任何登入使用者都能改動全系統 LDAP 設定或觸發連線測試(可能被用作內部網路探測工具)。與 _overview §5 一致,本 spec 只記錄不修(§3)。 - 連線測試失敗無專屬錯誤處理:FE
onTestConnectClick()只有.finally(重置 loading),沒有.catch顯示失敗訊息或聚焦欄位,錯誤 toast 完全依賴 axios 全域攔截器的通用文案;LOGIN_401014等具體原因不會被特別標示在表單上。 - 密碼「留白=沿用舊值」的判斷靠 FE
flag,不是靠是否為空字串:儲存走
changePwd(passwordHasChange)欄位判斷;連線測試走「payload 無secret但有user」判斷(ldap_service.py:43-45)——兩條路徑保留舊密碼的條件不同,若日後改動任一邏輯需同步檢視另一條。 is_ad決定認證機制而非單純顯示文字:AD 用 NTLM 認證且要求帳號格式DOMAIN\username(LOGIN_405003),OpenLDAP 走匿名或 simple bind 搜尋cn=;切換下拉不會清空既有帳號欄位,格式不符會在測試 / 登入時才報錯,非選擇當下即時驗證。encryption大小寫正規化:LdapAdapter內部一律.upper()比對,FE 下拉值固定送'SSL'/'STARTTLS'(大寫),與 BE 假設一致;但若日後有其他來源寫入小寫值(如手動改 DB),連線行為(是否啟用 STARTTLS)會因正規化前的舊版本差異而有雷(見套件內註解ldap_adapter.py:41-42,73,已修過一次大小寫不一致的坑)。- 設定不存在時的行為不對稱:
GET走get_system_config_by_key找不到會NotFound(SYSTEM_CONFIG_404001);PUT走update_config_by_group_key找不到則自動新增(非報錯)。首次使用本頁時,若 GET 400/404,FEgetConfig()沒有特別 catch,會直接留預設值(server:''/port:389/ ...),使用者未必會注意到「這是全新設定而非讀取失敗」。 system_configs表本身無created_user/updated_user欄位:不同於本專案「審計欄位回傳規範」(CLAUDE.md),本表無操作者留痕,異動追蹤需另查api_logs/system_logs(見操作日誌頁)。tenant_id/org_unit_id欄位存在但本頁未使用:LDAP 設定實務上是全域(root)層級參數,FE 從未傳入這兩欄,隱含「只有一份全租戶共用的 LDAP 設定」的假設;若未來要做多租戶各自 LDAP,需連同 §6 / §9 一起重新設計(非本頁現況涵蓋範圍)。
13. 開發與驗證
- 跑起來:BE
python main_socketio.py(port 8000,log 在log/app.log);FE 在compliance-manager-fe/起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload)。 - 測試帳號:見 memory
reference_dev_login/.env(憑證不寫入本文件);本頁無角色門檻,任一登入帳號皆可進頁測試。 - 導航路徑:登入 → 側選單「系統管理」→「LDAP
設定」(
/system/ldap-config)。 - 前置資料:無強制前置資料;首次進頁
system_configs若無THIRD_PARTY_LOGIN/LDAP列,GET 會 404(見 §12 坑 6),儲存後才會建立該列。 - 實機驗證連線測試:需要一台可連通的 LDAP/AD 測試伺服器;DEV 環境是否已備測試伺服器需另行確認(存疑項,見報告)。
- E2E:
compliance-manager-test/repo(Cucumber + Playwright);本頁相關 feature 檔以ldap搜尋(若尚無則屬待補)。 - 相關文件:
docs/claude/frontend-overview.md(BE 視角速查);GAI-SD-02/03 涵蓋常規 CRUD 端點細節。