权限系统 / 已完成
后台权限系统设计
理解用户、角色、权限、菜单、按钮权限、路由权限和后端接口权限如何组成权限闭环。
返回文章积累一句话:后台权限系统就是回答“谁登录了、他是什么角色、他能看到什么、能操作什么、后端要不要放行”。
本篇学完你会什么:理解用户、角色、权限、菜单、按钮权限、路由权限和后端接口权限之间的关系,知道怎么从最小版一步步做成可维护的后台权限系统。
1. 权限系统解决什么问题
后台系统不是所有人都能做所有事。
比如:
普通运营:只能看用户列表
客服主管:可以编辑用户状态
系统管理员:可以新增用户、分配角色、删除用户权限系统要回答四个问题:
你是谁?
你有哪些角色?
你的角色有哪些权限?
这个接口或按钮能不能给你用?2. 用户、角色、权限分别是什么
先用公司门禁理解:
| 名词 | 像什么 | 例子 |
|---|---|---|
| 用户 User | 具体的人 | 张三 |
| 角色 Role | 一张岗位牌 | 管理员、运营、客服 |
| 权限 Permission | 能做某件事的钥匙 | user:create |
| 菜单 Menu | 能看到哪个房间入口 | 用户管理菜单 |
| 按钮 Button | 房间里的具体操作 | 新增、删除 |
| 接口 API | 真正办事的窗口 | POST /users |
最重要的一句话:角色不是权限本身,角色只是权限的集合。
3. 最小数据模型怎么设计
最小可维护版建议 5 张表:
users
roles
permissions
user_roles
role_permissions关系是:
一个用户可以有多个角色
一个角色可以有多个权限
用户最终拥有的权限 = 他所有角色的权限合集如果第一版很小,也可以先简化:
users 表里加 is_admin但只适合学习 Demo。只要系统里开始有“运营、客服、管理员、只读人员”这些区别,就应该上角色和权限。
4. 权限码怎么命名
权限码要稳定、可读、方便搜索。
推荐格式:
资源:动作例子:
| 权限码 | 大白话 |
|---|---|
user:read | 查看用户 |
user:create | 新增用户 |
user:update | 编辑用户 |
user:delete | 删除用户 |
role:read | 查看角色 |
role:assign | 分配角色 |
permission:manage | 管理权限 |
不要用这种看不懂的名字:
p001
admin_user_btn_3
canDoSomething权限码以后会出现在前端路由、按钮、后端接口、数据库和日志里,名字越清楚,排查越省力。
5. 登录后要返回什么
登录成功后,后端可以返回:
{
"access_token": "eyJxxx",
"user": {
"id": 1,
"username": "admin"
},
"roles": ["admin"],
"permissions": [
"user:read",
"user:create",
"user:update",
"user:delete"
]
}前端拿到后:
- 保存 token。
- 保存用户信息。
- 保存权限码列表。
- 根据权限生成菜单、路由、按钮状态。
注意:这里的 permissions 更适合放在登录响应体里,或者放在 /auth/me 返回的数据里,给前端渲染菜单和按钮用。
不建议把所有权限码都塞进 JWT payload。JWT 里通常只放 sub 用户 id、exp 过期时间,最多再放一个 token version 这类轻量字段。权限变化时,服务端重新查询权限会更可控。
刷新页面后,前端不要只相信本地缓存,最好调用:
GET /api/v1/auth/me重新确认当前用户和权限。
6. 前端路由权限怎么做
路由可以写上需要的权限:
{
path: '/users',
component: () => import('@/pages/users/UserListPage.vue'),
meta: {
title: '用户管理',
permission: 'user:read'
}
}路由守卫检查:
router.beforeEach((to) => {
const permission = to.meta.permission as string | undefined;
if (permission && !authStore.permissions.includes(permission)) {
return '/403';
}
return true;
});大白话:进页面前先看门票,没有 user:read 就不能进用户管理页。
7. 前端按钮权限怎么做
按钮也可以按权限显示:
<button v-if="hasPermission('user:create')">
新增用户
</button>
<button v-if="hasPermission('user:delete')">
删除
</button>hasPermission 可以很简单:
export function hasPermission(code: string) {
return authStore.permissions.includes(code);
}注意:按钮隐藏只是用户体验,不是安全。真正安全要看后端。
8. 后端接口权限怎么做
后端接口也要声明需要什么权限:
def require_permission(code: str):
async def checker(current_user: User = Depends(get_current_user)):
permissions = await get_user_permissions(current_user.id)
if code not in permissions:
raise HTTPException(status_code=403, detail="没有权限")
return current_user
return checker使用:
@router.post("/users")
async def create_user(
body: UserCreate,
current_user: User = Depends(require_permission("user:create")),
):
...这一步是权限系统的底线。即使前端按钮被人绕过,后端也不能放行。
9. 菜单和权限是什么关系
菜单是入口,权限是钥匙。
有两种常见做法:
| 做法 | 说明 | 适合 |
|---|---|---|
| 前端固定菜单,根据权限过滤 | 菜单写在前端代码里 | 小项目、菜单变化少 |
| 后端返回菜单树 | 菜单存在数据库里 | 后台系统、菜单可配置 |
固定菜单例子:
const menus = [
{
title: '用户管理',
path: '/users',
permission: 'user:read'
}
];
const visibleMenus = menus.filter((menu) => hasPermission(menu.permission));后端菜单树例子:
[
{
"title": "系统管理",
"children": [
{
"title": "用户管理",
"path": "/users",
"permission": "user:read"
}
]
}
]第一版建议先用前端固定菜单。等菜单确实需要后台配置,再把菜单放到后端。
10. 从简单版到正式版怎么升级
不要一步到位。可以这样升级:
| 阶段 | 做法 | 适合什么时候 |
|---|---|---|
| 第 1 阶段 | is_admin 判断管理员 | 学习 Demo |
| 第 2 阶段 | 用户、角色、权限三层 | 有多个岗位 |
| 第 3 阶段 | 菜单权限、按钮权限、接口权限统一 | 正式后台 |
| 第 4 阶段 | 数据权限 | 不同部门只能看自己的数据 |
| 第 5 阶段 | 审计日志 | 要追踪谁改了什么 |
大多数项目先做到第 3 阶段就够用了。数据权限和审计日志可以后面再补。
11. 常见错误
| 错误 | 后果 | 修正 |
|---|---|---|
| 只做前端权限 | 接口可被直接调用 | 后端接口必须校验 |
| 权限码命名混乱 | 排查困难 | 用 资源:动作 |
| 把角色写死在代码里 | 后期难改 | 角色和权限放数据库 |
| 菜单权限和接口权限分离 | 看得到但点了 403 | 菜单、按钮、接口用同一套权限码 |
| JWT payload 里塞太多权限 | token 太大且权限变化不及时 | 可只放 user id,权限从服务端查 |
没有 /auth/me | 刷新后状态不可靠 | 前端启动时重新拉当前用户 |
12. 权限系统检查清单
[ ] 是否区分用户、角色、权限
[ ] 权限码是否用统一命名规则
[ ] 登录后能拿到当前用户权限
[ ] 刷新页面后能通过 /auth/me 恢复权限
[ ] 路由进入前会检查页面权限
[ ] 按钮会根据权限显示或隐藏
[ ] 后端接口会校验权限
[ ] 无权限时返回 403
[ ] 菜单和按钮使用同一套权限码
[ ] 管理员变更权限后有明确刷新策略13. 总结表
| 名词 | 大白话 |
|---|---|
| 用户 | 具体登录的人 |
| 角色 | 一组权限的集合 |
| 权限码 | 能不能做某件事的钥匙 |
| 菜单权限 | 能不能看到入口 |
| 按钮权限 | 能不能看到操作按钮 |
| 接口权限 | 后端是否真正放行 |
| 403 | 已登录,但没权限 |
/auth/me | 刷新后重新确认当前用户和权限 |
上一篇建议:大白话讲解——前后端接口联调.md
下一篇建议:回到真实项目,先用 user:read、user:create、user:update、user:delete 做一套最小权限闭环。