For AI agents: the complete documentation index is available at https://liangy0323.github.io/ly-fullstack/llms.txt, the full documentation bundle is available at https://liangy0323.github.io/ly-fullstack/llms-full.txt, and this page is available as Markdown at https://liangy0323.github.io/ly-fullstack/architecture/auth-rbac.md.

认证与 RBAC

LY Fullstack 的权限闭环由数据库、Admin API 和 Admin 三层共同组成。前端负责展示和交互,最终授权决定始终在服务端。

五表模型

核心关系为:

User ──< UserRole >── Role ──< RoleMenu >── Menu
  • User:管理员账号、状态、密码摘要和会话版本。
  • Role:角色标识、状态与系统保护属性。
  • Menu:目录、菜单、按钮权限及前端页面绑定信息。
  • UserRole:用户与角色多对多关系。
  • RoleMenu:角色与菜单/权限多对多关系。

超级管理员角色由 Seed 初始化并受保护,不应像普通业务角色一样被删除或削弱。

登录链路

  1. Admin 请求 GET /api/auth/captcha 获取服务端生成的缺口图和拼图块。
  2. 用户完成拖动后,Admin 请求 POST /api/auth/captcha/verify
  3. 验证通过会得到一次性凭证;正确位置不会直接下发给浏览器。
  4. Admin 携带账号、密码和一次性凭证请求 POST /api/auth/login
  5. Admin API 校验账号状态和密码摘要,签发管理 JWT。
  6. Pinia 保存认证状态,路由进入受保护区域。

验证码挑战与登录凭证均只能消费一次,并有接口限流。它提供基础的人机校验,不应被宣传为可替代专业风控系统。

会话恢复为什么还要查数据库

JWT 不保存完整角色、菜单和权限快照。浏览器刷新后调用 GET /api/auth/meAdminJwtGuard 根据账号标识重新读取:

  • 账号是否仍存在且启用;
  • Token 中的 tokenVersion 是否仍有效;
  • 当前角色和角色状态;
  • 当前菜单树和权限码。

因此,管理员禁用账号、调整角色或修改菜单权限后,不需要等待旧权限快照自然过期。

tokenVersion 如何撤销旧 Token

修改当前密码或由管理员重置密码时,服务端递增账号的 tokenVersion。旧 JWT 仍携带签发时的旧版本,下一次受保护请求会被 Guard 拒绝。

这解决账号级会话撤销,但不能替代 JWT 密钥轮换。JWT_SECRET 泄露时仍必须更换生产密钥并重启服务。

动态菜单和接口权限

一项功能通常同时具有:

  1. 静态路由:Admin 知道页面组件在哪里。
  2. 数据库菜单:当前角色是否在侧栏看到入口。
  3. 按钮权限码:页面是否展示新增、编辑、删除等操作。
  4. 服务端权限声明:直接请求接口时是否允许执行。

只有前三项没有第四项,攻击者仍可以绕过页面直接调用 API。所有敏感写操作都应使用 @RequirePermissions(...) 并由 PermissionGuard 校验。

测试一个新角色

建议保留一个非超级管理员测试账号:

  1. 在角色管理创建角色。
  2. 只勾选目标菜单和部分操作权限。
  3. 在用户管理把角色分配给测试账号。
  4. 使用无痕窗口登录该账号。
  5. 验证菜单和按钮隐藏。
  6. 使用浏览器网络面板或测试直接调用无权接口,确认返回 403

页面看不到入口只是体验验收;接口明确拒绝才是安全验收。

当前边界

这套认证只属于后台管理员。默认 apps/api 不包含终端用户注册、登录、刷新令牌、第三方 OAuth 或多租户会话,后续必须按真实 C 端业务单独设计。