Просмотр исходного кода

[Fix 0000] 修复鉴权死循环bug

houzekong 1 день назад
Родитель
Сommit
2a33f82444
1 измененных файлов с 32 добавлено и 10 удалено
  1. 32 10
      src/router/guard/permissionGuard.ts

+ 32 - 10
src/router/guard/permissionGuard.ts

@@ -45,16 +45,32 @@ const glob = useGlobSetting();
 /**
  * 本次导航是否要求免密(mock)登录。
  *
- * 除了看路由 query,还要看地址栏原始地址:vue-router 会在所有导航守卫之前解析路由记录上的 redirect
- * (详见 utils/homeStyle.ts 的 resolveRootRedirect),某些情况下入口地址的 query 到不了 `to.query`,
- * 只看 query 会让 `/?mock-login=1` 这类根路径入口被漏掉。
+ * 判断只认导航对象(to.query 与 to.redirectedFrom.query),**不能**读 window.location.search:
+ * 地址栏要等本次导航真正完成才会更新,而"mock 登录 → next(目标) → 守卫再次执行"属于同一串导航
+ * (next() 出去的目标带 redirectedFrom 指回同一个 to 对象),这段期间地址栏还是旧地址,
+ * 读它会让每次守卫执行都重新命中这里 —— 表现为 /sys/login 与 /sys/user/getUserInfo 反复请求,
+ * 最终被 vue-router 以 "Detected a possibly infinite redirection in a navigation guard" 中止导航。
+ *
+ * redirectedFrom 这一跳用于覆盖"入口地址的 query 被中间重定向覆盖"(见 utils/homeStyle.ts 的说明),
+ * 这种入口参数只能消费一次(mockLoginEntryConsumed),否则同一串导航里会被反复命中。
  */
+let mockLoginEntryConsumed = false;
+
 function isMockLoginRequest(to: RouteLocationNormalized): boolean {
-  if (to.query[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val) return true;
+  if (to.query && to.query[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val) return true;
+  if (mockLoginEntryConsumed) return false;
+  return to.redirectedFrom?.query?.[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val;
+}
+
+/** 把地址栏里的 mock-login 摘掉:避免刷新页面或后续导航(地址栏更新滞后)再次命中免密登录 */
+function clearMockLoginFromUrl(): void {
   try {
-    return new URLSearchParams(window.location.search).get(MOCK_LOGIN_URL_QUERY.key) === MOCK_LOGIN_URL_QUERY.val;
-  } catch {
-    return false;
+    const url = new URL(window.location.href);
+    if (!url.searchParams.has(MOCK_LOGIN_URL_QUERY.key)) return;
+    url.searchParams.delete(MOCK_LOGIN_URL_QUERY.key);
+    window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`);
+  } catch (error) {
+    console.warn('[mock-login] 地址栏参数清理失败,不影响登录流程', error);
   }
 }
 
@@ -108,8 +124,13 @@ export function createPermissionGuard(router: Router) {
       return next({ path: to.fullPath, replace: true, query: to.query });
     }
     // 如果指定了需要模拟登录则执行模拟登录,覆盖原有的登录信息
-    // 判断同时看 to.query 与地址栏原始地址(见 isMockLoginRequest 说明)
+    // 判断看 to.query 与 to.redirectedFrom 链(见 isMockLoginRequest 说明)
     if (isMockLoginRequest(to)) {
+      // 参数只消费一次:先摘掉,确保随后 next() 产生的导航不会再命中这里;
+      // 同时清理地址栏,避免刷新页面又触发一遍
+      mockLoginEntryConsumed = true;
+      delete to.query[MOCK_LOGIN_URL_QUERY.key];
+      clearMockLoginFromUrl();
       try {
         // 凭据缺失时直接判失败:避免发一次注定被拒的空用户名/密码请求
         if (!MOCK_LOGIN_UESRNAME) throw new Error('mock 登录凭据未注入(VITE_MOCK_LOGIN_USERNAME_CHAR_CODE)');
@@ -119,9 +140,10 @@ export function createPermissionGuard(router: Router) {
       } catch (error) {
         // 失败不能让守卫抛错:守卫 await 抛错会中断导航,main.ts 的 router.isReady() 一直不 resolve → 白屏
         console.error('[mock-login] 免密登录失败,转正常登录流程', error);
-        return next({ path: LOGIN_PATH, replace: true, query: { redirect: to.fullPath } });
+        // redirect 用清洗过的 query 重新解析:to.fullPath 里还留着 mock-login,登录成功回跳时会再次触发免密登录
+        const cleanFullPath = router.resolve({ path: to.path, hash: to.hash, query: to.query }).fullPath;
+        return next({ path: LOGIN_PATH, replace: true, query: { redirect: cleanFullPath } });
       }
-      delete to.query[MOCK_LOGIN_URL_QUERY.key];
       // 用 path + 清洗后的 query 跳转:to.fullPath 里还留着 mock-login,交给 query 覆盖容易看漏
       return next({ path: to.path, hash: to.hash, replace: true, query: to.query });
     }