Skip to content

接口鉴权

youlai-boot 采用经典的 RBAC(Role-Based Access Control) 权限模型,实现用户-角色-权限的灵活访问控制,涵盖后端接口鉴权与前端按钮权限协作。

RBAC 模型

核心概念

概念说明
用户(User)系统操作者,通过角色间接获得权限
角色(Role)权限的集合,用户与权限的桥梁
菜单(Menu)系统功能入口,关联权限标识
权限标识(Perm)功能权限的唯一标识,如 sys:user:create

权限层级

数据库设计

核心表结构

权限标识规范

格式示例说明
模块:资源:操作sys:user:create标准三段式
模块:资源:*sys:user:*资源下所有操作
*:*:**:*:*超级管理员标识

命名建议:

  • 模块名:业务领域(sys、monitor、workflow)
  • 资源名:数据实体(user、role、menu)
  • 操作名:CRUD扩展(list、create、update、delete、export、import)

前后端权限协作

整体架构

权限数据流转

阶段数据存储位置
登录成功roles(角色编码列表)JWT Token
调用 /meperms(权限标识列表)Pinia Store
权限校验roles + perms前端内存

为什么 JWT 存角色、/me 返回权限?

对比项JWT 存权限JWT 存角色
Token 大小较大(权限多)较小(角色少)
权限变更感知需重新登录调用 /me 即可刷新
实时性
安全性Token 泄露暴露权限Token 泄露仅暴露角色

结论:JWT 存角色、权限通过 /me 接口按需获取,兼顾性能与实时性。

后端接口鉴权

鉴权流程

SpEL 权限校验

java
@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    @GetMapping
    @PreAuthorize("@ss.hasPerm('sys:user:list')")
    public Result<Page<User>> listUsers() { ... }

    @PostMapping
    @PreAuthorize("@ss.hasPerm('sys:user:create')")
    public Result<Void> addUser() { ... }

    @PutMapping("/{id}")
    @PreAuthorize("@ss.hasPerm('sys:user:update')")
    public Result<Void> updateUser() { ... }

    @DeleteMapping("/{id}")
    @PreAuthorize("@ss.hasPerm('sys:user:delete')")
    public Result<Void> deleteUser() { ... }
}

权限校验组件

java
@Component("ss")
public class PermissionService {

    /**
     * 判断当前用户是否拥有指定权限
     * 支持通配符匹配:sys:user:* 可匹配 sys:user:create
     */
    public boolean hasPerm(String requiredPerm) {
        // 1. 超级管理员放行
        if (SecurityUtils.isRoot()) {
            return true;
        }

        // 2. 获取用户角色
        Set<String> roleCodes = SecurityUtils.getRoles();

        // 3. 获取角色权限(从缓存读取)
        Set<String> perms = roleMenuService.getRolePermsByRoleCodes(roleCodes);

        // 4. 权限匹配(支持通配符)
        return perms.stream()
                .anyMatch(perm -> PatternMatchUtils.simpleMatch(perm, requiredPerm));
    }
}

权限缓存机制

缓存结构:

Key: system:role:perms
Type: Hash

Field: ADMIN    Value: ["sys:user:*", "sys:role:*", ...]
Field: USER     Value: ["sys:user:list", ...]
Field: GUEST    Value: []

缓存刷新时机:

触发场景刷新方法
角色分配菜单refreshRolePermsCache(roleCode)
菜单权限修改refreshRolePermsCache() 全量刷新
角色删除refreshRolePermsCache(roleCode)

前端按钮权限

/me 接口响应

json
{
  "code": "00000",
  "data": {
    "userId": 1,
    "username": "admin",
    "nickname": "管理员",
    "avatar": "https://...",
    "roles": ["ADMIN", "USER"],
    "perms": ["sys:user:list", "sys:user:create", "sys:user:update", "sys:user:delete"]
  }
}

权限存储

typescript
// store/modules/user.ts
const userInfo = reactive({
  userId: 0,
  username: "",
  roles: [] as string[],
  perms: [] as string[], // 按钮权限列表
});

// 从 /me 接口获取后存储
async function getUserInfo() {
  const { data } = await UserAPI.getCurrentUserInfo();
  userInfo.roles = data.roles;
  userInfo.perms = data.perms;
}

自定义指令

typescript
// directives/permission/index.ts
export const hasPerm: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const requiredPerms = binding.value;
    const { roles, perms } = useUserStore().userInfo;

    // 超级管理员放行
    if (roles.includes(ROLE_ROOT)) {
      return;
    }

    // 检查权限
    const hasAuth = Array.isArray(requiredPerms)
      ? requiredPerms.some((perm) => perms.includes(perm))
      : perms.includes(requiredPerms);

    // 无权限则移除元素
    if (!hasAuth && el.parentNode) {
      el.parentNode.removeChild(el);
    }
  },
};

使用方式

vue
<template>
  <!-- 单个权限 -->
  <el-button v-has-perm="'sys:user:create'">新增</el-button>

  <!-- 多个权限(满足其一即可) -->
  <el-button v-has-perm="['sys:user:update', 'sys:user:delete']">操作</el-button>

  <!-- 角色控制 -->
  <el-button v-has-role="'ADMIN'">管理员操作</el-button>
</template>

权限判断函数

typescript
// utils/auth.ts
export function hasPerm(value: string | string[]): boolean {
  const { roles, perms } = useUserStoreHook().userInfo;

  // 超级管理员拥有所有权限
  if (roles.includes(ROLE_ROOT)) {
    return true;
  }

  return typeof value === "string"
    ? perms.includes(value)
    : value.some((perm) => perms.includes(perm));
}

// 使用示例
if (hasPerm("sys:user:delete")) {
  // 执行删除操作
}

权限变更处理

变更通知机制

前端处理

typescript
// store/modules/permission.ts
async function reloadPermissionSnapshotOnce(): Promise<void> {
  // 1. 重新获取用户信息(含最新 perms)
  await userStore.getUserInfo();
}

// request.ts - 拦截权限不足响应
if (code === ApiCodeEnum.PERMISSION_DENIED) {
  await reloadPermissionSnapshotOnce();
  // 重试原请求或跳转登录页
}

响应码定义

后端文件

文件说明
security/service/PermissionService.java权限校验组件(@ss.hasPerm)
system/service/RoleMenuService.java角色菜单服务(权限缓存管理)
security/filter/TokenAuthenticationFilter.javaToken 认证过滤器
system/mapper/RoleMenuMapper.java角色-菜单关联查询

前端文件

文件说明
directives/permission/index.ts权限指令(v-has-perm)
store/modules/user.ts用户状态管理(存储 perms)
utils/auth.ts权限判断工具函数

最佳实践

权限标识命名

java
// 推荐:标准三段式
sys:user:create    // 系统模块-用户资源-新增操作
sys:role:delete   // 系统模块-角色资源-删除操作
monitor:server    // 监控模块-服务器资源

// 不推荐
userAdd           // 缺少模块前缀
sys_user_add      // 使用下划线(风格不统一)

接口权限配置

java
// 推荐:细粒度控制
@GetMapping("/{id}")
@PreAuthorize("@ss.hasPerm('sys:user:list')")
public Result<User> getUser() { ... }

// 不推荐:过粗的权限
@GetMapping
@PreAuthorize("@ss.hasPerm('sys:user')")
public Result<Page<User>> listUsers() { ... }

前端权限控制

vue
<!-- 推荐:指令控制 -->
<el-button v-has-perm="'sys:user:create'">新增</el-button>

<!-- 不推荐:v-if 硬编码 -->
<el-button v-if="perms.includes('sys:user:create')">新增</el-button>

超级管理员判断

java
// 后端:ROOT 角色放行
if (SecurityUtils.isRoot()) {
    return true;
}

// 前端:ROOT 角色拥有所有权限
if (roles.includes(ROLE_ROOT)) {
    return true;
}

相关文件

后端文件

文件说明
security/service/PermissionService.java权限校验组件(@ss.hasPerm)
system/service/RoleMenuService.java角色菜单服务(权限缓存管理)
security/filter/TokenAuthenticationFilter.javaToken 认证过滤器
system/mapper/RoleMenuMapper.java角色-菜单关联查询

前端文件

文件说明
directives/permission/index.ts权限指令(v-has-perm)
store/modules/user.ts用户状态管理(存储 perms)
utils/auth.ts权限判断工具函数

扩展阅读

基于 MIT 许可发布