liuq 1 месяц назад
Родитель
Сommit
31ef2abbed
2 измененных файлов с 92 добавлено и 7 удалено
  1. 48 7
      backend/app/api/v1/endpoints/apps.py
  2. 44 0
      frontend/src/views/apps/AppList.vue

+ 48 - 7
backend/app/api/v1/endpoints/apps.py

@@ -148,17 +148,23 @@ def create_app(
     # 如果是 OIDC 应用,自动在 Hydra 中创建 / 更新对应的 OAuth2 Client
     if db_app.protocol_type == ProtocolType.OIDC:
         try:
+            raw = db_app.redirect_uris or ""
             redirect_uris: list[str] = []
-            if db_app.redirect_uris:
+
+            if raw:
+                # 1. 优先按 JSON 解析(支持 ["url1","url2"] 或 "url1")
                 try:
-                    parsed = json.loads(db_app.redirect_uris)
+                    parsed = json.loads(raw)
                     if isinstance(parsed, list):
-                        redirect_uris = parsed
+                        redirect_uris = [str(u).strip() for u in parsed if str(u).strip()]
                     elif isinstance(parsed, str):
-                        redirect_uris = [parsed]
+                        if parsed.strip():
+                            redirect_uris = [parsed.strip()]
                 except Exception:
-                    # 非 JSON 格式时直接作为单个 URI 使用
-                    redirect_uris = [db_app.redirect_uris.strip()]
+                    # 2. 非 JSON 时,支持逗号分隔或单个 URL
+                    parts = [u.strip() for u in raw.split(",") if u.strip()]
+                    if parts:
+                        redirect_uris = parts
 
             hydra_service.create_or_update_client(
                 client_id=db_app.app_id,
@@ -166,7 +172,7 @@ def create_app(
                 redirect_uris=redirect_uris,
                 client_name=db_app.app_name or db_app.app_id,
             )
-            logger.info(f"Hydra OIDC Client 已创建/更新: {db_app.app_id}")
+            logger.info(f"Hydra OIDC Client 已创建/更新: {db_app.app_id}, redirect_uris={redirect_uris}")
         except Exception as e:
             logger.exception(
                 "应用创建成功,但在 Hydra 创建 OIDC Client 失败 (app_id=%s): %s",
@@ -216,6 +222,41 @@ def update_app(
     db.commit()
     db.refresh(app)
 
+    # 如果是 OIDC 应用,编辑后同步 Hydra 中的 OAuth2 Client
+    if app.protocol_type == ProtocolType.OIDC:
+        try:
+            raw = app.redirect_uris or ""
+            redirect_uris: list[str] = []
+
+            if raw:
+                # 1. 优先按 JSON 解析(支持 ["url1","url2"] 或 "url1")
+                try:
+                    parsed = json.loads(raw)
+                    if isinstance(parsed, list):
+                        redirect_uris = [str(u).strip() for u in parsed if str(u).strip()]
+                    elif isinstance(parsed, str):
+                        if parsed.strip():
+                            redirect_uris = [parsed.strip()]
+                except Exception:
+                    # 2. 非 JSON 时,支持逗号分隔或单个 URL
+                    parts = [u.strip() for u in raw.split(",") if u.strip()]
+                    if parts:
+                        redirect_uris = parts
+
+            hydra_service.create_or_update_client(
+                client_id=app.app_id,
+                client_secret=app.app_secret,
+                redirect_uris=redirect_uris,
+                client_name=app.app_name or app.app_id,
+            )
+            logger.info(f"Hydra OIDC Client 已在编辑后同步: {app.app_id}, redirect_uris={redirect_uris}")
+        except Exception as e:
+            logger.exception(
+                "应用编辑成功,但在 Hydra 同步 OIDC Client 失败 (app_id=%s): %s",
+                app.app_id,
+                e,
+            )
+
     # 5. Log
     LogService.create_log(
         db=db,

+ 44 - 0
frontend/src/views/apps/AppList.vue

@@ -358,6 +358,38 @@ const secretData = reactive({
   access_token: ''
 })
 
+// ===== 工具函数:回调地址校验 =====
+const isValidUrl = (u: string) => {
+  try {
+    const url = new URL(u)
+    return url.protocol === 'http:' || url.protocol === 'https:'
+  } catch {
+    return false
+  }
+}
+
+const validateRedirectUris = (raw: string): boolean => {
+  if (!raw || !raw.trim()) return true // 允许为空
+
+  // 1. 先尝试 JSON 解析
+  try {
+    const parsed = JSON.parse(raw)
+    if (Array.isArray(parsed)) {
+      return parsed.every(x => typeof x === 'string' && isValidUrl(x.trim()))
+    }
+    if (typeof parsed === 'string') {
+      return isValidUrl(parsed.trim())
+    }
+  } catch {
+    // 2. 非 JSON 时,当作逗号分隔 / 单个 URL
+    const parts = raw.split(',').map(x => x.trim()).filter(Boolean)
+    if (parts.length === 0) return true
+    return parts.every(isValidUrl)
+  }
+
+  return false
+}
+
 // Methods
 const fetchApps = async () => {
   loading.value = true
@@ -411,6 +443,12 @@ const handleEdit = (row: Application) => {
 
 const handleSubmit = async () => {
   try {
+    // 校验回调地址合法性
+    if (!validateRedirectUris(form.redirect_uris || '')) {
+      ElMessage.error('回调地址格式不合法,请填写以 http/https 开头的完整 URL,可用逗号分隔多个或 JSON 列表')
+      return
+    }
+
     if (isEdit.value && currentId.value) {
       // For Edit, open security dialog
       securityAction.value = 'EDIT'
@@ -466,6 +504,12 @@ const confirmSecurityAction = async () => {
     processing.value = true
     try {
         if (securityAction.value === 'EDIT' && currentId.value) {
+            // 编辑时再次校验回调地址
+            if (!validateRedirectUris(form.redirect_uris || '')) {
+                ElMessage.error('回调地址格式不合法,请填写以 http/https 开头的完整 URL,可用逗号分隔多个或 JSON 列表')
+                processing.value = false
+                return
+            }
             // Merge form data with security data (exclude password)
             await updateApp(currentId.value, {
                 ...form,