Appearance
路由守卫与权限控制
概述
路由守卫是保护应用安全和控制用户访问的关键机制。在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 };
}总结
路由守卫和权限控制是应用安全的重要组成部分:
- 认证守卫:确保用户已登录
- 角色控制:基于用户角色限制访问
- 权限控制:细粒度的功能权限检查
- 动态权限:运行时权限验证
- 多层守卫:组合多种守卫策略
- 用户体验:友好的错误提示和引导
正确实现路由守卫能够构建安全可靠的应用访问控制系统。