解决Ant Design Pro登录页跳转循环:从根源修复重定向死锁

解决Ant Design Pro登录页跳转循环:从根源修复重定向死锁

【免费下载链接】ant-design-pro 👨🏻‍💻👩🏻‍💻 Use Ant Design like a Pro! 【免费下载链接】ant-design-pro 项目地址: https://gitcode.com/gh_mirrors/an/ant-design-pro

你是否遇到过这样的情况:在Ant Design Pro中输入正确的账号密码后,页面却不断在登录页和目标页之间来回跳转,形成令人崩溃的无限循环?这种"登录跳转死锁"不仅影响用户体验,更是后台管理系统常见的安全隐患。本文将通过3个典型场景分析、4步调试方案和2种根治策略,帮助你彻底解决这一问题。

问题场景:为什么会陷入跳转循环?

登录跳转循环本质上是认证状态与路由权限不匹配导致的系统"认知混乱"。以下是3种最常见的触发场景:

场景1:认证状态未正确持久化

当用户登录成功后,认证信息(如Token)未能正确存储到本地存储(LocalStorage/SessionStorage),导致页面刷新后系统重新判定为未登录状态。查看src/pages/user/login/index.tsx的登录逻辑:

// 登录成功后的重定向逻辑
if (msg.status === 'ok') {
  message.success(defaultLoginSuccessMessage);
  await fetchUserInfo();
  const urlParams = new URL(window.location.href).searchParams;
  window.location.href = urlParams.get('redirect') || '/'; // 直接跳转未等待状态保存
  return;
}

这段代码中,window.location.href的跳转操作可能在认证状态完全保存前就执行了,导致目标页面检测不到有效的登录状态而重定向回登录页。

场景2:路由权限配置冲突

config/routes.ts中,管理员路由配置了权限校验:

{
  path: '/admin',
  name: 'admin',
  icon: 'crown',
  access: 'canAdmin', // 权限校验
  routes: [
    {
      path: '/admin',
      redirect: '/admin/sub-page',
    },
    // ...
  ],
}

如果canAdmin权限判断逻辑存在缺陷(如未正确读取用户角色),会导致已登录用户被判定为无权限访问目标页面,从而触发重定向回登录页的循环。

场景3:登录状态同步延迟

Ant Design Pro使用Umi的全局状态管理(@umijs/max的useModel)存储登录状态。在src/pages/user/login/index.tsx中:

const { initialState, setInitialState } = useModel('@@initialState');

const fetchUserInfo = async () => {
  const userInfo = await initialState?.fetchUserInfo?.();
  if (userInfo) {
    flushSync(() => { // 使用flushSync确保状态同步更新
      setInitialState((s) => ({
        ...s,
        currentUser: userInfo,
      }));
    });
  }
};

如果省略flushSync或状态更新存在延迟,目标页面可能在状态更新完成前就执行了权限检查,导致系统误判用户未登录。

诊断工具:4步定位问题根源

步骤1:追踪重定向链条

打开浏览器开发者工具(F12),切换到网络标签页,勾选"保留日志"(Preserve log)选项,然后完成登录流程。观察网络请求中的302重定向响应,记录完整的跳转路径。正常情况下应该是:

/user/login/welcome (或其他目标页)

而异常情况下会出现:

/user/login/welcome/user/login/welcome...

步骤2:检查认证存储状态

在开发者工具的应用标签页中,查看本地存储会话存储,确认是否存在名为tokenuserInfo的键值对。Ant Design Pro通常在src/services/ant-design-pro/login.ts中处理认证信息存储:

/** 发送验证码 POST /api/login/captcha */
export async function getFakeCaptcha(
  params: { phone?: string },
  options?: { [key: string]: any },
) {
  return request<API.FakeCaptcha>('/api/login/captcha', {
    method: 'GET',
    params: { ...params },
    ...(options || {}),
  });
}

注意:实际项目中应在登录API调用后添加token存储逻辑,如localStorage.setItem('token', response.token)

步骤3:分析路由守卫逻辑

检查路由配置中的wrappers属性,确认是否存在全局路由守卫。典型的权限校验包装组件可能位于src/wrappers/auth.tsx,会类似这样实现:

export default function AuthWrapper(props) {
  const { initialState } = useModel('@@initialState');
  const { currentUser } = initialState;
  
  if (!currentUser) {
    // 未登录,重定向到登录页
    return <Navigate to={`/user/login?redirect=${props.location.pathname}`} />;
  }
  
  return <props.children />;
}

步骤4:调试状态更新时机

src/pages/user/login/index.tsx的登录成功回调中添加断点:

// 登录成功后的处理
if (msg.status === 'ok') {
  message.success(defaultLoginSuccessMessage);
  await fetchUserInfo(); // 断点1:检查用户信息是否正确获取
  const urlParams = new URL(window.location.href).searchParams;
  const targetUrl = urlParams.get('redirect') || '/';
  console.log('即将跳转:', targetUrl, '当前用户状态:', initialState.currentUser); // 断点2:确认跳转前状态已更新
  window.location.href = targetUrl;
  return;
}

通过断点调试确认initialState.currentUser在跳转前已正确设置。

解决方案:从修复到根治

紧急修复方案:增加状态确认延迟

在必须立即解决问题的情况下,可以在跳转前增加一个微小的延迟,确保状态更新完成:

// 修改src/pages/user/login/index.tsx的登录成功逻辑
if (msg.status === 'ok') {
  message.success(defaultLoginSuccessMessage);
  await fetchUserInfo();
  const urlParams = new URL(window.location.href).searchParams;
  const targetUrl = urlParams.get('redirect') || '/';
  
  // 增加100ms延迟确保状态同步
  setTimeout(() => {
    window.location.href = targetUrl;
  }, 100);
  
  return;
}

这种方法简单有效,但不是根本解决方案,适合生产环境临时修复。

根治策略1:实现基于状态的路由跳转

替换传统的window.location.href跳转方式,使用React Router的编程式导航,并增加状态检查:

// 在src/pages/user/login/index.tsx中导入useNavigate
import { useNavigate } from 'react-router-dom';

// 在组件中获取导航实例
const navigate = useNavigate();

// 修改登录成功后的跳转逻辑
if (msg.status === 'ok') {
  message.success(defaultLoginSuccessMessage);
  await fetchUserInfo();
  
  // 确保用户状态已正确设置
  if (initialState.currentUser) {
    const urlParams = new URL(window.location.href).searchParams;
    navigate(urlParams.get('redirect') || '/');
  } else {
    message.error('登录状态同步失败,请重试');
  }
  return;
}

根治策略2:优化认证状态管理

在src/app.tsx中配置全局初始状态,确保认证信息持久化:

// src/app.tsx
export async function getInitialState(): Promise<{
  currentUser?: API.CurrentUser;
  fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
}> {
  // 从本地存储恢复认证状态
  const token = localStorage.getItem('token');
  if (token) {
    try {
      // 验证token有效性并获取用户信息
      const fetchUserInfo = async () => {
        const response = await request('/api/currentUser');
        return response.data;
      };
      const currentUser = await fetchUserInfo();
      return {
        currentUser,
        fetchUserInfo,
      };
    } catch (error) {
      // token无效,清除存储
      localStorage.removeItem('token');
    }
  }
  
  return {
    currentUser: undefined,
  };
}

预防措施:3个最佳实践

1. 实现防重定向循环机制

在路由守卫中添加跳转循环检测,当短时间内检测到多次连续跳转时自动终止:

// src/wrappers/auth.tsx
const [redirectCount, setRedirectCount] = useState(0);

useEffect(() => {
  // 5秒内重定向超过3次则判定为循环
  if (redirectCount > 3) {
    message.error('检测到异常跳转循环,请刷新页面重试');
    // 清除可能导致循环的错误状态
    localStorage.removeItem('token');
    setRedirectCount(0);
  }
}, [redirectCount]);

// 在重定向时增加计数
if (!currentUser) {
  setRedirectCount(prev => prev + 1);
  return <Navigate to={`/user/login?redirect=${props.location.pathname}`} />;
}

2. 完善权限测试用例

为登录和权限相关逻辑添加单元测试,确保覆盖各种边界情况:

// src/pages/user/login/login.test.tsx
describe('Login Page', () => {
  it('should redirect to target page after login', async () => {
    // 模拟登录API返回成功
    mockLogin.mockResolvedValue({ status: 'ok' });
    
    // 渲染登录组件
    render(<Login />);
    
    // 输入账号密码并提交
    fireEvent.change(screen.getByPlaceholderText('用户名: admin or user'), {
      target: { value: 'admin' },
    });
    fireEvent.change(screen.getByPlaceholderText('密码: ant.design'), {
      target: { value: 'ant.design' },
    });
    fireEvent.click(screen.getByRole('button', { name: /登录/i }));
    
    // 验证是否跳转到欢迎页
    await waitFor(() => {
      expect(window.location.href).toContain('/welcome');
    });
  });
});

3. 使用统一的认证工具函数

创建src/utils/auth.ts封装所有认证相关操作,避免状态管理分散:

// src/utils/auth.ts
export const Auth = {
  // 存储认证信息
  setToken(token: string) {
    localStorage.setItem('token', token);
  },
  
  // 获取认证信息
  getToken() {
    return localStorage.getItem('token');
  },
  
  // 清除认证信息
  clearToken() {
    localStorage.removeItem('token');
  },
  
  // 验证认证状态
  async validate() {
    const token = this.getToken();
    if (!token) return false;
    
    try {
      const response = await request('/api/validateToken', {
        headers: { Authorization: `Bearer ${token}` },
      });
      return response.status === 'ok';
    } catch (error) {
      this.clearToken();
      return false;
    }
  }
};

Ant Design Pro 登录流程图

通过以上方法,你不仅能够解决当前的登录跳转循环问题,还能建立起一套健壮的认证状态管理机制,有效预防类似问题的再次发生。记住,良好的状态管理和路由设计是企业级应用稳定性的基石。

官方文档:README.md 路由配置:config/routes.ts 登录组件源码:src/pages/user/login/index.tsx 认证服务:src/services/ant-design-pro/login.ts

【免费下载链接】ant-design-pro 👨🏻‍💻👩🏻‍💻 Use Ant Design like a Pro! 【免费下载链接】ant-design-pro 项目地址: https://gitcode.com/gh_mirrors/an/ant-design-pro

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值