一、权限系统为什么难做?
大多数人以为权限就是“控制能不能访问页面”,但企业级项目(尤其如广告平台)涉及的是:
- 路由权限
- 页面元素权限(按钮/操作)
- 接口权限
- 数据权限(作用域)
- 权限变更后的同步与清除
一个真正健壮的权限系统,需要“动态+精细+可维护”,否则权限绕过、漏判、混乱就是家常便饭。
二、权限模型抽象
我们抽象出三类权限:
interface Permission {
menus: string[]; // 路由菜单权限
elements: string[]; // 按钮/组件权限
actions: string[]; // 接口操作权限
}
服务端返回格式:
{
"menus": ["/ad-plan", "/material"],
"elements": ["ad:create", "ad:delete"],
"actions": ["CREATE_AD", "DELETE_AD"]
}
三、动态路由权限
- 登录后拉取权限信息:
const res = await getUserPermissions()
const { menus, elements, actions } = res.data
- 动态构造前端路由表:
// 全量路由表
const allRoutes = [
{ path: '/ad-plan', meta: { permission: '/ad-plan' }, component: AdPlan },
{ path: '/material', meta: { permission: '/material' }, component: Material }
]
const filteredRoutes = allRoutes.filter(r => menus.includes(r.meta.permission))
filteredRoutes.forEach(route => router.addRoute(route))
四、按钮级权限控制(元素级)
封装自定义指令 v-permission:
app.directive('permission', {
mounted(el, binding) {
const userStore = useUserStore()
if (!userStore.elements.includes(binding.value)) {
el.parentNode && el.parentNode.removeChild(el)
}
}
})
使用方式:
<n-button v-permission="'ad:delete'">删除</n-button>
或统一封装权限按钮组件:
<PermissionButton code="ad:create">新建</PermissionButton>
五、接口权限控制(行为级)
接口访问前自动附加权限信息,用于后端二次校验。
Axios 拦截器注入请求标识:
axios.interceptors.request.use(config => {
config.headers['X-User-Actions'] = userStore.actions.join(',')
return config
})
六、权限变更与缓存更新
问题:某用户被移除某权限后,再次访问仍能看到旧页面或按钮?
解决:权限变更后需彻底刷新客户端状态:
const reloadPermission = async () => {
const res = await getUserPermissions()
userStore.$patch(res.data)
resetRoutes() // 移除旧权限路由
generateRoutes() // 重新添加新路由
router.replace('/dashboard')
}
七、常见权限问题与解法
1. 用户手动输入 URL 越权访问?
➡ 使用导航守卫拦截:
router.beforeEach((to, _, next) => {
if (!userStore.menus.includes(to.path)) {
return next('/403')
}
next()
})
2. 刷新页面权限丢失?
➡ 将权限缓存到 localStorage,刷新后恢复
const init = () => {
const cached = localStorage.getItem('PERMISSION')
if (cached) userStore.$patch(JSON.parse(cached))
else fetchPermissions()
}
3. 多角色用户权限重叠时如何取交集?
➡ 后端应返回融合后的权限,前端无需合并逻辑
八、权限系统测试建议
权限系统容易遗漏,因此建议写权限单元测试或 E2E:
it('should not render button if no permission', () => {
userStore.elements = []
const wrapper = mount(Component)
expect(wrapper.find('button').exists()).toBe(false)
})
九、总结:权限系统四字诀:可查、可配、可控、可变
- 可查:日志记录、定位问题
- 可配:权限点集中配置,利于管理
- 可控:页面、接口、组件都有校验逻辑
- 可变:支持权限变更实时响应
权限系统并不难,但如果做得浅,很容易成为系统最大的安全漏洞源头。