Skip to content

路由守卫与权限控制

概述

路由守卫是保护应用安全和控制用户访问的关键机制。在React Router中,我们可以通过组件包装、条件渲染、导航拦截等方式实现路由级别的权限控制,确保用户只能访问其有权限的页面。

基础路由保护

认证守卫

最基础的路由保护是检查用户是否已登录:

jsx
import { Navigate, useLocation } from 'react-router-dom';

// 基础的认证守卫组件
function RequireAuth({ children }) {
  const { user, isLoading } = useAuth();
  const location = useLocation();

  if (isLoading) {
    return <LoadingSpinner />;
  }

  if (!user) {
    // 重定向到登录页面,并保存当前位置
    return (
      <Navigate 
        to="/login" 
        state={{ from: location.pathname + location.search }}
        replace 
      />
    );
  }

  return children;
}

// 在路由中使用
function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="login" element={<Login />} />
        <Route path="register" element={<Register />} />
        
        {/* 需要认证的路由 */}
        <Route 
          path="dashboard" 
          element={
            <RequireAuth>
              <Dashboard />
            </RequireAuth>
          } 
        />
        
        <Route 
          path="profile" 
          element={
            <RequireAuth>
              <Profile />
            </RequireAuth>
          } 
        />
        
        {/* 批量保护嵌套路由 */}
        <Route 
          path="app/*" 
          element={
            <RequireAuth>
              <AppRoutes />
            </RequireAuth>
          } 
        />
      </Route>
    </Routes>
  );
}

// 登录页面处理重定向
function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  
  // 获取登录前的位置
  const from = location.state?.from || '/dashboard';

  const handleLogin = async (credentials) => {
    try {
      const user = await login(credentials);
      
      // 登录成功后返回原始页面
      navigate(from, { replace: true });
      
    } catch (error) {
      setError('Invalid credentials');
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <h1>Login</h1>
      <p>Please sign in to continue to {from}</p>
      
      {/* 登录表单 */}
      <input type="email" placeholder="Email" />
      <input type="password" placeholder="Password" />
      <button type="submit">Sign In</button>
      
      <p>
        Don't have an account? 
        <Link to="/register" state={{ from }}>Register here</Link>
      </p>
    </form>
  );
}

角色基础的守卫

jsx
// 角色守卫组件
function RequireRole({ children, allowedRoles, fallback }) {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (!allowedRoles.includes(user.role)) {
    return fallback || (
      <Navigate 
        to="/access-denied" 
        state={{ 
          requiredRoles: allowedRoles,
          currentRole: user.role,
          attemptedPath: location.pathname
        }}
        replace 
      />
    );
  }

  return children;
}

// 权限守卫组件
function RequirePermission({ children, permission, fallback }) {
  const { user, permissions } = useAuth();
  const location = useLocation();

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (!permissions.includes(permission)) {
    return fallback || (
      <Navigate 
        to="/access-denied" 
        state={{ 
          requiredPermission: permission,
          userPermissions: permissions,
          attemptedPath: location.pathname
        }}
        replace 
      />
    );
  }

  return children;
}

// 组合守卫组件
function RequireRoleAndPermission({ children, role, permission }) {
  return (
    <RequireRole allowedRoles={[role]}>
      <RequirePermission permission={permission}>
        {children}
      </RequirePermission>
    </RequireRole>
  );
}

// 在路由中使用
function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        {/* 公开路由 */}
        <Route index element={<Home />} />
        <Route path="login" element={<Login />} />
        
        {/* 需要登录的路由 */}
        <Route 
          path="dashboard" 
          element={
            <RequireAuth>
              <Dashboard />
            </RequireAuth>
          } 
        />
        
        {/* 需要管理员角色的路由 */}
        <Route 
          path="admin/*" 
          element={
            <RequireRole allowedRoles={['admin', 'super_admin']}>
              <AdminRoutes />
            </RequireRole>
          } 
        />
        
        {/* 需要特定权限的路由 */}
        <Route 
          path="users/manage" 
          element={
            <RequirePermission permission="users:write">
              <UserManagement />
            </RequirePermission>
          } 
        />
        
        {/* 需要角色和权限的路由 */}
        <Route 
          path="system/settings" 
          element={
            <RequireRoleAndPermission role="admin" permission="system:config">
              <SystemSettings />
            </RequireRoleAndPermission>
          } 
        />
      </Route>
    </Routes>
  );
}

高级权限控制

动态权限检查

jsx
// 动态权限Hook
function usePermissions() {
  const { user } = useAuth();
  const [permissions, setPermissions] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (user) {
      fetchUserPermissions(user.id)
        .then(setPermissions)
        .finally(() => setLoading(false));
    } else {
      setPermissions([]);
      setLoading(false);
    }
  }, [user]);

  const hasPermission = useCallback((permission) => {
    return permissions.includes(permission);
  }, [permissions]);

  const hasAnyPermission = useCallback((permissionList) => {
    return permissionList.some(permission => permissions.includes(permission));
  }, [permissions]);

  const hasAllPermissions = useCallback((permissionList) => {
    return permissionList.every(permission => permissions.includes(permission));
  }, [permissions]);

  const canAccess = useCallback((resource, action) => {
    const requiredPermission = `${resource}:${action}`;
    return hasPermission(requiredPermission);
  }, [hasPermission]);

  return {
    permissions,
    loading,
    hasPermission,
    hasAnyPermission,
    hasAllPermissions,
    canAccess
  };
}

// 资源权限守卫
function ResourceGuard({ children, resource, action, fallback }) {
  const { canAccess, loading } = usePermissions();

  if (loading) {
    return <LoadingSpinner />;
  }

  if (!canAccess(resource, action)) {
    return fallback || <AccessDenied resource={resource} action={action} />;
  }

  return children;
}

// 条件权限守卫
function ConditionalGuard({ children, condition, fallback }) {
  const { user, permissions } = useAuth();

  const shouldAllow = useMemo(() => {
    if (typeof condition === 'function') {
      return condition(user, permissions);
    }
    return Boolean(condition);
  }, [condition, user, permissions]);

  if (!shouldAllow) {
    return fallback || <AccessDenied />;
  }

  return children;
}

// 使用示例
function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        {/* 基于资源和动作的权限控制 */}
        <Route 
          path="users" 
          element={
            <ResourceGuard resource="users" action="read">
              <UsersList />
            </ResourceGuard>
          } 
        />
        
        <Route 
          path="users/new" 
          element={
            <ResourceGuard resource="users" action="create">
              <CreateUser />
            </ResourceGuard>
          } 
        />
        
        <Route 
          path="users/:id/edit" 
          element={
            <ResourceGuard resource="users" action="update">
              <EditUser />
            </ResourceGuard>
          } 
        />
        
        {/* 复杂条件权限控制 */}
        <Route 
          path="reports/sensitive" 
          element={
            <ConditionalGuard
              condition={(user, permissions) => 
                user.role === 'admin' || 
                (user.department === 'finance' && permissions.includes('reports:sensitive'))
              }
            >
              <SensitiveReports />
            </ConditionalGuard>
          } 
        />
      </Route>
    </Routes>
  );
}

时间基础的访问控制

jsx
// 时间窗口守卫
function TimeWindowGuard({ children, allowedHours, timeZone = 'UTC' }) {
  const [currentTime, setCurrentTime] = useState(new Date());
  const [isAllowed, setIsAllowed] = useState(false);

  useEffect(() => {
    const checkTimeWindow = () => {
      const now = new Date();
      const currentHour = now.toLocaleString('en-US', {
        hour: 'numeric',
        hour12: false,
        timeZone
      });

      const hour = parseInt(currentHour, 10);
      setIsAllowed(allowedHours.includes(hour));
      setCurrentTime(now);
    };

    checkTimeWindow();
    
    // 每分钟检查一次
    const interval = setInterval(checkTimeWindow, 60000);
    
    return () => clearInterval(interval);
  }, [allowedHours, timeZone]);

  if (!isAllowed) {
    return (
      <div className="time-restriction">
        <h2>Access Restricted</h2>
        <p>This page is only accessible during business hours ({allowedHours.join(', ')}).</p>
        <p>Current time: {currentTime.toLocaleString()}</p>
      </div>
    );
  }

  return children;
}

// IP基础的访问控制
function IPGuard({ children, allowedIPs, blockedIPs }) {
  const [userIP, setUserIP] = useState(null);
  const [isAllowed, setIsAllowed] = useState(true);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 获取用户IP地址
    fetchUserIP()
      .then(ip => {
        setUserIP(ip);
        
        // 检查是否在黑名单中
        if (blockedIPs && blockedIPs.includes(ip)) {
          setIsAllowed(false);
          return;
        }
        
        // 检查是否在白名单中
        if (allowedIPs && !allowedIPs.includes(ip)) {
          setIsAllowed(false);
          return;
        }
        
        setIsAllowed(true);
      })
      .finally(() => setLoading(false));
  }, [allowedIPs, blockedIPs]);

  if (loading) {
    return <LoadingSpinner />;
  }

  if (!isAllowed) {
    return (
      <div className="ip-restriction">
        <h2>Access Denied</h2>
        <p>Your IP address ({userIP}) is not authorized to access this page.</p>
      </div>
    );
  }

  return children;
}

// 设备基础的访问控制
function DeviceGuard({ children, allowedDevices, blockedDevices }) {
  const deviceInfo = useDeviceDetection();
  
  const isAllowed = useMemo(() => {
    if (blockedDevices && blockedDevices.includes(deviceInfo.type)) {
      return false;
    }
    
    if (allowedDevices && !allowedDevices.includes(deviceInfo.type)) {
      return false;
    }
    
    return true;
  }, [deviceInfo, allowedDevices, blockedDevices]);

  if (!isAllowed) {
    return (
      <div className="device-restriction">
        <h2>Device Not Supported</h2>
        <p>This page is not available on {deviceInfo.type} devices.</p>
        <p>Please use a supported device to access this content.</p>
      </div>
    );
  }

  return children;
}

实战权限系统

RBAC (Role-Based Access Control)

jsx
// 角色权限映射
const ROLE_PERMISSIONS = {
  guest: [],
  user: ['profile:read', 'profile:update', 'orders:read'],
  moderator: ['profile:read', 'profile:update', 'orders:read', 'content:moderate'],
  admin: ['*'], // 所有权限
  super_admin: ['*']
};

// 权限检查工具
class PermissionChecker {
  constructor(userRole, userPermissions = []) {
    this.userRole = userRole;
    this.rolePermissions = ROLE_PERMISSIONS[userRole] || [];
    this.userPermissions = userPermissions;
    this.allPermissions = [...this.rolePermissions, ...this.userPermissions];
  }

  hasPermission(permission) {
    return this.allPermissions.includes('*') || 
           this.allPermissions.includes(permission);
  }

  hasAnyPermission(permissions) {
    return permissions.some(permission => this.hasPermission(permission));
  }

  hasAllPermissions(permissions) {
    return permissions.every(permission => this.hasPermission(permission));
  }

  canAccessResource(resource, action) {
    return this.hasPermission(`${resource}:${action}`);
  }

  canAccessPath(path) {
    const pathPermissions = {
      '/admin': ['admin:access'],
      '/users/manage': ['users:write'],
      '/reports/sensitive': ['reports:sensitive'],
      '/system/config': ['system:config']
    };

    const requiredPermissions = pathPermissions[path];
    if (!requiredPermissions) return true;

    return this.hasAnyPermission(requiredPermissions);
  }
}

// RBAC守卫组件
function RBACGuard({ children, requiredPermissions, requireAll = false }) {
  const { user } = useAuth();
  const location = useLocation();

  const checker = useMemo(() => {
    return new PermissionChecker(user?.role, user?.permissions);
  }, [user]);

  const hasAccess = useMemo(() => {
    if (!requiredPermissions || requiredPermissions.length === 0) {
      return true;
    }

    return requireAll 
      ? checker.hasAllPermissions(requiredPermissions)
      : checker.hasAnyPermission(requiredPermissions);
  }, [checker, requiredPermissions, requireAll]);

  if (!user) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (!hasAccess) {
    return (
      <Navigate 
        to="/access-denied" 
        state={{ 
          requiredPermissions,
          userRole: user.role,
          userPermissions: user.permissions
        }}
        replace 
      />
    );
  }

  return children;
}

// 使用RBAC守卫
function AdminPanel() {
  return (
    <Routes>
      <Route path="/" element={<AdminLayout />}>
        {/* 需要管理员访问权限 */}
        <Route 
          index 
          element={
            <RBACGuard requiredPermissions={['admin:access']}>
              <AdminDashboard />
            </RBACGuard>
          } 
        />
        
        {/* 需要用户管理权限 */}
        <Route 
          path="users" 
          element={
            <RBACGuard requiredPermissions={['users:read']}>
              <UserManagement />
            </RBACGuard>
          } 
        />
        
        {/* 需要多个权限(任一即可) */}
        <Route 
          path="reports" 
          element={
            <RBACGuard 
              requiredPermissions={['reports:read', 'analytics:read']}
              requireAll={false}
            >
              <Reports />
            </RBACGuard>
          } 
        />
        
        {/* 需要多个权限(全部必需) */}
        <Route 
          path="system" 
          element={
            <RBACGuard 
              requiredPermissions={['system:read', 'system:write']}
              requireAll={true}
            >
              <SystemSettings />
            </RBACGuard>
          } 
        />
      </Route>
    </Routes>
  );
}

动态权限控制

jsx
// 动态权限加载
function useDynamicPermissions(resource) {
  const { user } = useAuth();
  const [permissions, setPermissions] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (user && resource) {
      loadResourcePermissions(user.id, resource)
        .then(setPermissions)
        .finally(() => setLoading(false));
    }
  }, [user, resource]);

  return { permissions, loading };
}

// 动态权限守卫
function DynamicPermissionGuard({ children, resource, action }) {
  const { permissions, loading } = useDynamicPermissions(resource);

  if (loading) {
    return <LoadingSpinner />;
  }

  const hasPermission = permissions.some(p => 
    p.action === action && p.resource === resource
  );

  if (!hasPermission) {
    return <AccessDenied resource={resource} action={action} />;
  }

  return children;
}

// 基于数据的权限控制
function DataPermissionGuard({ children, dataId, action, resourceType }) {
  const { user } = useAuth();
  const [hasAccess, setHasAccess] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    checkDataAccess(user.id, dataId, action, resourceType)
      .then(setHasAccess)
      .finally(() => setLoading(false));
  }, [user.id, dataId, action, resourceType]);

  if (loading) {
    return <LoadingSpinner />;
  }

  if (!hasAccess) {
    return (
      <AccessDenied 
        message={`You don't have permission to ${action} this ${resourceType}`}
      />
    );
  }

  return children;
}

// 使用数据权限守卫
function EditDocument() {
  const { documentId } = useParams();

  return (
    <DataPermissionGuard
      dataId={documentId}
      action="edit"
      resourceType="document"
    >
      <DocumentEditor documentId={documentId} />
    </DataPermissionGuard>
  );
}

条件路由渲染

jsx
// 权限感知路由组件
function ConditionalRoute({ path, element, condition, fallback, ...props }) {
  const { user, permissions } = useAuth();

  const shouldRender = useMemo(() => {
    if (typeof condition === 'function') {
      return condition(user, permissions);
    }
    return Boolean(condition);
  }, [condition, user, permissions]);

  if (!shouldRender) {
    return fallback ? <Route path={path} element={fallback} {...props} /> : null;
  }

  return <Route path={path} element={element} {...props} />;
}

// 权限感知的路由配置
function AppRoutes() {
  const { user } = useAuth();

  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        
        {/* 基础用户功能 */}
        <ConditionalRoute
          path="profile"
          element={<Profile />}
          condition={(user) => Boolean(user)}
          fallback={<Navigate to="/login" replace />}
        />
        
        {/* 付费用户功能 */}
        <ConditionalRoute
          path="premium"
          element={<PremiumFeatures />}
          condition={(user) => user?.plan === 'premium'}
          fallback={<UpgradePlan />}
        />
        
        {/* 管理员功能 */}
        <ConditionalRoute
          path="admin/*"
          element={<AdminRoutes />}
          condition={(user) => ['admin', 'super_admin'].includes(user?.role)}
          fallback={<AccessDenied />}
        />
        
        {/* 测试功能(仅开发环境) */}
        {process.env.NODE_ENV === 'development' && (
          <Route path="dev-tools" element={<DevTools />} />
        )}
        
        {/* 实验性功能 */}
        <ConditionalRoute
          path="experimental"
          element={<ExperimentalFeatures />}
          condition={(user) => user?.betaTester === true}
        />
      </Route>
    </Routes>
  );
}

路由级中间件

导航中间件系统

jsx
// 导航中间件类型
const middlewareTypes = {
  BEFORE_NAVIGATE: 'BEFORE_NAVIGATE',
  AFTER_NAVIGATE: 'AFTER_NAVIGATE',
  NAVIGATE_ERROR: 'NAVIGATE_ERROR'
};

// 中间件管理器
class NavigationMiddleware {
  constructor() {
    this.middleware = {
      [middlewareTypes.BEFORE_NAVIGATE]: [],
      [middlewareTypes.AFTER_NAVIGATE]: [],
      [middlewareTypes.NAVIGATE_ERROR]: []
    };
  }

  use(type, middleware) {
    if (this.middleware[type]) {
      this.middleware[type].push(middleware);
    }
  }

  async execute(type, context) {
    const middlewares = this.middleware[type] || [];
    
    for (const middleware of middlewares) {
      try {
        const result = await middleware(context);
        
        // 如果中间件返回false,停止执行
        if (result === false) {
          return false;
        }
        
        // 如果中间件修改了上下文,使用新的上下文
        if (result && typeof result === 'object') {
          context = { ...context, ...result };
        }
      } catch (error) {
        console.error('Navigation middleware error:', error);
        
        // 执行错误中间件
        await this.execute(middlewareTypes.NAVIGATE_ERROR, { ...context, error });
        
        return false;
      }
    }
    
    return context;
  }
}

const navigationMiddleware = new NavigationMiddleware();

// 添加中间件
navigationMiddleware.use(middlewareTypes.BEFORE_NAVIGATE, async (context) => {
  console.log('Navigating to:', context.to);
  
  // 权限检查中间件
  if (!await checkRoutePermission(context.user, context.to)) {
    throw new Error('Access denied');
  }
  
  return context;
});

navigationMiddleware.use(middlewareTypes.BEFORE_NAVIGATE, async (context) => {
  // 数据预加载中间件
  if (context.preload) {
    await preloadRouteData(context.to);
  }
  
  return context;
});

navigationMiddleware.use(middlewareTypes.AFTER_NAVIGATE, async (context) => {
  // 分析追踪中间件
  analytics.track('page_view', {
    from: context.from,
    to: context.to,
    userId: context.user?.id
  });
  
  return context;
});

// 使用中间件的导航Hook
function useMiddlewareNavigation() {
  const navigate = useNavigate();
  const location = useLocation();
  const { user } = useAuth();

  const navigateWithMiddleware = useCallback(async (to, options = {}) => {
    const context = {
      from: location.pathname,
      to,
      user,
      options,
      timestamp: Date.now()
    };

    try {
      // 执行before中间件
      const beforeResult = await navigationMiddleware.execute(
        middlewareTypes.BEFORE_NAVIGATE,
        context
      );

      if (beforeResult === false) {
        return; // 导航被阻止
      }

      // 执行实际导航
      navigate(to, options);

      // 执行after中间件
      await navigationMiddleware.execute(
        middlewareTypes.AFTER_NAVIGATE,
        { ...context, success: true }
      );

    } catch (error) {
      // 执行错误中间件
      await navigationMiddleware.execute(
        middlewareTypes.NAVIGATE_ERROR,
        { ...context, error }
      );
    }
  }, [navigate, location, user]);

  return { navigateWithMiddleware };
}

路由级生命周期

jsx
// 路由生命周期Hook
function useRouteLifecycle() {
  const location = useLocation();
  const [routeState, setRouteState] = useState({
    isEntering: false,
    isLeaving: false,
    hasEntered: false,
    error: null
  });

  const routeLifecycle = useMemo(() => ({
    onEnter: [],
    onLeave: [],
    onError: []
  }), []);

  // 添加生命周期监听器
  const addLifecycleListener = useCallback((event, callback) => {
    if (routeLifecycle[event]) {
      routeLifecycle[event].push(callback);
    }
  }, [routeLifecycle]);

  // 执行生命周期回调
  const executeLifecycleCallbacks = useCallback(async (event, context) => {
    const callbacks = routeLifecycle[event] || [];
    
    for (const callback of callbacks) {
      try {
        await callback(context);
      } catch (error) {
        console.error(`Route lifecycle error (${event}):`, error);
        setRouteState(prev => ({ ...prev, error }));
      }
    }
  }, [routeLifecycle]);

  useEffect(() => {
    const enterRoute = async () => {
      setRouteState(prev => ({ ...prev, isEntering: true }));

      try {
        await executeLifecycleCallbacks('onEnter', {
          pathname: location.pathname,
          search: location.search,
          state: location.state
        });

        setRouteState(prev => ({ 
          ...prev, 
          isEntering: false, 
          hasEntered: true 
        }));

      } catch (error) {
        setRouteState(prev => ({ 
          ...prev, 
          isEntering: false, 
          error 
        }));
      }
    };

    enterRoute();

    // 清理函数 - 离开路由时执行
    return () => {
      setRouteState(prev => ({ ...prev, isLeaving: true }));
      
      executeLifecycleCallbacks('onLeave', {
        pathname: location.pathname
      });
    };
  }, [location, executeLifecycleCallbacks]);

  return {
    routeState,
    addLifecycleListener
  };
}

// 使用路由生命周期
function AnalyticsPage() {
  const { addLifecycleListener } = useRouteLifecycle();
  const [data, setData] = useState(null);

  useEffect(() => {
    // 添加进入路由时的回调
    addLifecycleListener('onEnter', async (context) => {
      console.log('Entered analytics page:', context.pathname);
      
      // 预加载数据
      const analyticsData = await loadAnalyticsData();
      setData(analyticsData);
      
      // 记录访问
      trackPageAccess('analytics');
    });

    // 添加离开路由时的回调
    addLifecycleListener('onLeave', (context) => {
      console.log('Leaving analytics page');
      
      // 清理资源
      cleanupAnalyticsSubscriptions();
      
      // 保存用户操作
      saveUserSession(context.pathname);
    });
  }, [addLifecycleListener]);

  return (
    <div className="analytics-page">
      <h1>Analytics Dashboard</h1>
      {data ? <AnalyticsCharts data={data} /> : <LoadingSpinner />}
    </div>
  );
}

复杂权限场景

多租户权限控制

jsx
// 租户权限守卫
function TenantGuard({ children }) {
  const { tenantId } = useParams();
  const { user } = useAuth();
  const [tenantAccess, setTenantAccess] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (user && tenantId) {
      checkTenantAccess(user.id, tenantId)
        .then(setTenantAccess)
        .finally(() => setLoading(false));
    }
  }, [user, tenantId]);

  if (loading) {
    return <LoadingSpinner />;
  }

  if (!tenantAccess || !tenantAccess.hasAccess) {
    return (
      <div className="tenant-access-denied">
        <h2>Access Denied</h2>
        <p>You don't have access to this tenant's data.</p>
        {tenantAccess?.reason && (
          <p>Reason: {tenantAccess.reason}</p>
        )}
      </div>
    );
  }

  return (
    <TenantContext.Provider value={{ tenantId, tenantAccess }}>
      {children}
    </TenantContext.Provider>
  );
}

// 租户感知的路由
function TenantAwareRoutes() {
  return (
    <Routes>
      <Route path="/tenant/:tenantId" element={<TenantGuard><TenantLayout /></TenantGuard>}>
        <Route index element={<TenantDashboard />} />
        
        <Route path="users" element={<TenantUserManagement />} />
        <Route path="settings" element={<TenantSettings />} />
        <Route path="billing" element={<TenantBilling />} />
        
        {/* 租户管理员功能 */}
        <Route 
          path="admin" 
          element={
            <RequireRole allowedRoles={['tenant_admin']}>
              <TenantAdminPanel />
            </RequireRole>
          } 
        />
      </Route>
    </Routes>
  );
}

功能开关控制

jsx
// 功能开关Hook
function useFeatureFlags() {
  const { user } = useAuth();
  const [flags, setFlags] = useState({});

  useEffect(() => {
    if (user) {
      loadFeatureFlags(user.id)
        .then(setFlags);
    }
  }, [user]);

  const isFeatureEnabled = useCallback((flagName) => {
    return flags[flagName] === true;
  }, [flags]);

  const hasFeatureAccess = useCallback((flagName, fallbackCondition) => {
    if (flags.hasOwnProperty(flagName)) {
      return flags[flagName];
    }
    
    return fallbackCondition ? fallbackCondition(user) : false;
  }, [flags, user]);

  return {
    flags,
    isFeatureEnabled,
    hasFeatureAccess
  };
}

// 功能开关守卫
function FeatureGuard({ children, feature, fallback }) {
  const { isFeatureEnabled } = useFeatureFlags();

  if (!isFeatureEnabled(feature)) {
    return fallback || (
      <div className="feature-disabled">
        <h2>Feature Not Available</h2>
        <p>This feature is currently not enabled for your account.</p>
      </div>
    );
  }

  return children;
}

// A/B测试路由
function ABTestRoute({ path, variants, defaultVariant, ...props }) {
  const { user } = useAuth();
  const [variant, setVariant] = useState(defaultVariant);

  useEffect(() => {
    if (user) {
      getABTestVariant(user.id, path)
        .then(setVariant);
    }
  }, [user, path]);

  const selectedVariant = variants[variant] || variants[defaultVariant];

  return <Route path={path} element={selectedVariant} {...props} />;
}

// 使用功能开关和A/B测试
function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        
        {/* 功能开关控制的路由 */}
        <Route 
          path="new-feature" 
          element={
            <FeatureGuard 
              feature="new_ui_enabled"
              fallback={<ComingSoon />}
            >
              <NewFeaturePage />
            </FeatureGuard>
          } 
        />
        
        {/* A/B测试路由 */}
        <ABTestRoute
          path="landing"
          variants={{
            'control': <LandingPageControl />,
            'variant_a': <LandingPageVariantA />,
            'variant_b': <LandingPageVariantB />
          }}
          defaultVariant="control"
        />
        
        {/* Beta功能 */}
        <Route 
          path="beta/*" 
          element={
            <FeatureGuard 
              feature="beta_features"
              fallback={<BetaSignup />}
            >
              <BetaRoutes />
            </FeatureGuard>
          } 
        />
      </Route>
    </Routes>
  );
}

权限控制最佳实践

1. 权限检查策略

jsx
// 客户端权限检查(用于UI优化)
function ClientPermissionCheck({ permission, children, fallback }) {
  const { hasPermission } = usePermissions();

  if (!hasPermission(permission)) {
    return fallback || null;
  }

  return children;
}

// 服务端权限验证(真正的安全检查)
function ServerPermissionGuard({ children, resource, action }) {
  const [verified, setVerified] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 向服务端验证权限
    verifyPermissionWithServer(resource, action)
      .then(setVerified)
      .finally(() => setLoading(false));
  }, [resource, action]);

  if (loading) return <LoadingSpinner />;
  if (!verified) return <AccessDenied />;

  return children;
}

// 双重权限检查
function SecureRoute({ children, permission, resource, action }) {
  return (
    <ClientPermissionCheck permission={permission}>
      <ServerPermissionGuard resource={resource} action={action}>
        {children}
      </ServerPermissionGuard>
    </ClientPermissionCheck>
  );
}

2. 错误处理和用户体验

jsx
// 友好的访问拒绝页面
function AccessDenied({ requiredPermission, userRole, message }) {
  const navigate = useNavigate();
  const { user } = useAuth();

  const handleContactSupport = () => {
    navigate('/support/contact', {
      state: {
        reason: 'access_denied',
        requiredPermission,
        currentRole: userRole,
        requestedPath: location.pathname
      }
    });
  };

  const handleUpgradeAccount = () => {
    navigate('/upgrade', {
      state: {
        reason: 'permission_required',
        requiredPermission
      }
    });
  };

  return (
    <div className="access-denied">
      <div className="access-denied-content">
        <h1>Access Denied</h1>
        
        <p>
          {message || 
           `You don't have permission to access this page. Required permission: ${requiredPermission}`
          }
        </p>

        {user ? (
          <div className="access-denied-actions">
            <p>Current role: <strong>{user.role}</strong></p>
            
            <button onClick={handleUpgradeAccount}>
              Upgrade Account
            </button>
            
            <button onClick={handleContactSupport}>
              Contact Support
            </button>
            
            <button onClick={() => navigate(-1)}>
              Go Back
            </button>
          </div>
        ) : (
          <div className="access-denied-actions">
            <p>Please sign in to access this page.</p>
            
            <button onClick={() => navigate('/login', {
              state: { from: location.pathname }
            })}>
              Sign In
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

// 权限提示组件
function PermissionHint({ permission, children }) {
  const { hasPermission } = usePermissions();

  if (hasPermission(permission)) {
    return children;
  }

  return (
    <div className="permission-hint">
      <p>You need the '{permission}' permission to access this feature.</p>
      <button onClick={() => navigate('/upgrade')}>
        Upgrade Account
      </button>
    </div>
  );
}

3. 权限缓存和优化

jsx
// 权限缓存Hook
function usePermissionsCache() {
  const { user } = useAuth();
  const [permissionsCache, setPermissionsCache] = useState(new Map());

  const getPermissions = useCallback(async (resource) => {
    const cacheKey = `${user.id}-${resource}`;
    
    if (permissionsCache.has(cacheKey)) {
      const cached = permissionsCache.get(cacheKey);
      
      // 检查缓存是否过期(5分钟)
      if (Date.now() - cached.timestamp < 300000) {
        return cached.permissions;
      }
    }

    // 从服务器获取权限
    const permissions = await fetchResourcePermissions(user.id, resource);
    
    // 更新缓存
    setPermissionsCache(prev => new Map(prev).set(cacheKey, {
      permissions,
      timestamp: Date.now()
    }));

    return permissions;
  }, [user, permissionsCache]);

  const clearCache = useCallback((resource) => {
    if (resource) {
      const cacheKey = `${user.id}-${resource}`;
      setPermissionsCache(prev => {
        const newCache = new Map(prev);
        newCache.delete(cacheKey);
        return newCache;
      });
    } else {
      setPermissionsCache(new Map());
    }
  }, [user]);

  return { getPermissions, clearCache };
}

// 批量权限检查
function useBatchPermissions(permissionsList) {
  const [results, setResults] = useState({});
  const [loading, setLoading] = useState(true);
  const { user } = useAuth();

  useEffect(() => {
    if (user && permissionsList.length > 0) {
      // 批量检查权限
      batchCheckPermissions(user.id, permissionsList)
        .then(permissionsMap => {
          setResults(permissionsMap);
        })
        .finally(() => setLoading(false));
    }
  }, [user, permissionsList]);

  return { permissions: results, loading };
}

总结

路由守卫和权限控制是应用安全的重要组成部分:

  1. 认证守卫:确保用户已登录
  2. 角色控制:基于用户角色限制访问
  3. 权限控制:细粒度的功能权限检查
  4. 动态权限:运行时权限验证
  5. 多层守卫:组合多种守卫策略
  6. 用户体验:友好的错误提示和引导

正确实现路由守卫能够构建安全可靠的应用访问控制系统。