权限设计不是单点技巧,而是一套“模型 → 同步 → 前端落点 → 后端兜底”的体系。本文结合实际项目,总结四种常见前端落点:路由守卫、按钮/组件权限、动态路由、接口权限控制,并给出各自优劣与实现要点。
一、先立模型:资源、动作与主体
- 主体(Subject):用户、角色、组织、租户等。
- 资源(Resource):页面、菜单、按钮、接口、文件等。
- 动作(Action):view/create/update/delete/export…
- 常见模型:
- RBAC:角色-权限映射,简单高效;粒度通常到菜单/按钮/接口。
- ABAC:基于属性的策略更灵活(时间段、地理、部门级别等),实现与维护更复杂。
建议以 RBAC 起步,保留扩展位点(在权限项中附带资源/动作/条件)。前端仅做“展示与导航层过滤”,真正的安全必须后端校验。
二、路由权限控制(Route Guard)
— 页面级拦截入口。
- 优点:统一把关,能有效阻止未授权页面访问;配合路由元信息,规则可读性强。
- 局限:必须与后端权限同步,单靠前端不安全;需为每个受限路由显式声明。
1)Vue Router 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { router } from './router' import { useAuthStore } from '@/stores/auth'
router.beforeEach((to, _from, next) => { const auth = useAuthStore() const requiresAuth = Boolean(to.meta?.requiresAuth) if (!requiresAuth) return next()
if (!auth.user) { return next({ name: 'login', query: { redirect: to.fullPath } }) }
const needRoles = (to.meta?.roles as string[] | undefined) ?? [] if (needRoles.length && !needRoles.some(r => auth.user!.roles.includes(r))) { return next({ name: '403' }) } next() })
|
路由元信息示例:
1 2 3 4 5 6
| { path: '/admin', name: 'admin', component: () => import('@/pages/Admin.vue'), meta: { requiresAuth: true, roles: ['admin'] } }
|
2)React Router 示例
1 2 3 4 5 6 7 8 9 10 11 12
| import { Navigate } from 'react-router-dom' import { useAuth } from './auth'
export function Guard({ roles, children }: { roles?: string[]; children: React.ReactNode }) { const { user } = useAuth() if (!user) return <Navigate to="/login" replace /> if (roles && !roles.some(r => user.roles.includes(r))) return <Navigate to="/403" replace /> return <>{children}</> }
|
要点:路由层做“页面级”拦截,所有受限页面都需声明 meta.roles
或通过包装组件传入 roles
。
三、按钮/组件权限控制(Fine-grained UI)
— 细粒度的页面内显隐/禁用。
- 优点:就地控制,用户体验最佳;权限变化可即时反映到交互元素。
- 局限:需要在多个位置加判断,开发与维护成本较高;仅影响可见性,不提供真正的安全保证。
1)Vue 自定义指令
1 2 3 4 5 6 7 8 9 10 11 12
| import { useAuthStore } from '@/stores/auth'
export default { mounted(el: HTMLElement, binding: { value: string | string[] }) { const auth = useAuthStore() const need = ([] as string[]).concat(binding.value || []) const has = need.some(code => auth.perms.includes(code)) if (!has) el.parentNode?.removeChild(el) } }
|
2)React 组件封装
1 2 3 4 5 6 7 8
| import { useAuth } from './auth'
export function Can({ perm, children }: { perm: string; children: React.ReactNode }) { const { perms } = useAuth() if (!perms?.includes(perm)) return null return <>{children}</> }
|
要点:UI 层只负责“显隐/禁用”,不要在前端做安全假设;真正的权限校验在后端接口。
四、动态路由加载(Login 后按权限注入)
— 登录后按权限注入路由与菜单。
- 优点:减少无权限页面的代码与加载时间;与菜单/导航天然统一。
- 局限:实现与状态同步更复杂,需要 404/403 兜底与缓存恢复策略。
1)Vue 动态注入
1 2 3 4 5 6
| const { data: serverRoutes } = await api.get('/me/routes') serverRoutes.forEach((r: any) => { router.addRoute(mapToVueRoute(r)) })
|
2)React Router 构建路由表
1 2 3 4 5 6 7
| import { useMemo } from 'react' import { useRoutes } from 'react-router-dom'
function AppRoutes({ rawRoutes }: { rawRoutes: any[] }) { const routes = useMemo(() => buildRoutes(rawRoutes), [rawRoutes]) return useRoutes(routes) }
|
要点:动态路由可减少无权限页面的打包体积与加载时间,但需要配合菜单构建、缓存恢复与 404/403 兜底处理。
五、接口权限控制(后端兜底、安全边界)
— 真正的安全边界在后端。
- 优点:最安全、可审计、可细化到资源实例级;防止接口被恶意调用。
- 局限:前端无法替代,需后端网关/服务配合与策略下发。
前端在请求中携带凭证(Cookie/JWT/Bearer token),后端网关/服务侧做鉴权与鉴定;前端只负责处理 401/403 与跳转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| axios.interceptors.request.use((cfg) => { const token = getToken() if (token) cfg.headers.Authorization = `Bearer ${token}` return cfg })
axios.interceptors.response.use(undefined, (err) => { if (err.response?.status === 401) { logout() location.assign(`/login?redirect=${encodeURIComponent(location.pathname + location.search)}`) } return Promise.reject(err) })
|
后端建议:
- 在 JWT 的 claims 中放入
roles
/perms
/tenant
等只读信息;关键写操作仍以服务端 ACL/Policy 为准。
- 对敏感接口做二次校验(如资源拥有者校验、操作幂等签名、防重放)。
六、权限数据同步与一致性
- 登录后获取
userInfo + roles + perms + menus/routes
,并缓存到 Store;支持刷新恢复。
- 变更时(角色变更、强制下线)通过推送/轮询刷新权限。
- 前端每次进入受限页面二次校验(例如检查
token
是否过期、角色是否仍然匹配)。
七、统一的权限元信息约定
建议在路由/菜单项中统一描述权限:
1 2 3 4 5
| type Meta = { requiresAuth?: boolean roles?: string[] perms?: string[] }
|
示例:
1 2 3 4
| { path: '/user/list', meta: { requiresAuth: true, roles: ['admin','ops'], perms: ['user:list','user:export'] } }
|
八、总结
- 页面进入前由“路由守卫”兜底;页面内用“按钮/组件权限”做细粒度体验优化;配合“动态路由”减少无关代码;真正的安全由“接口权限控制”在后端完成。
- 前端权限只负责“看得见/点得着”,不可替代后端鉴权;两端需共享统一的权限模型与数据。
Happy Coding!