# 1 RBAC 权限模型

RBAC(Role-Based Access Control,基于角色的访问控制)是一种广泛使用的权限管理模型,它通过角色作为中介来连接用户和权限。

RBAC

在企业管理系统设计中,采用RBAC(基于角色的访问控制)模型能大幅简化权限管理。我们不再需要为每个用户单独设置权限,而是先抽象出"销售经理"、"财务经理"、"市场经理"等角色,将权限批量分配给这些角色。

当员工入职或调岗时,只需调整其对应的角色即可自动获得相应权限。例如,同时负责销售和市场部门的王先生,只需被赋予"销售经理"和"市场经理"两个角色,就能自动继承这两个角色的所有权限。

这种方式既避免了逐个设置权限的繁琐,又便于后续权限的批量调整,使权限管理更加灵活高效。

下图展示了系统中用户、角色、菜单和权限之间的关系。

# 2 权限注解

项目使用 Spring Security 作为安全验证框架。 Spring Security 是 Spring 生态中的安全框架,用于:

  • 认证(Authentication):验证用户身份(如用户名/密码登录)
  • 授权(Authorization):控制用户能访问哪些资源(如 /admin 仅管理员可访问)

@PreAuthorize 是 Spring Security 提供的前置权限控制注解,用于在方法执行前检查权限,通常标注在 Controller 或 Service 层的方法上。

1、基于【权限标识】的权限控制

权限标识,对应 system_menu 表的 permission 字段,推荐格式为 ${系统}:${模块}:${操作},例如说 system:admin:add 标识 system 服务的添加管理员。

使用示例如下:

// 符合 system:user:list 权限要求
@PreAuthorize("@ss.hasPermission('system:user:list')")

// 符合 system:user:add 或 system:user:edit 权限要求即可
@PreAuthorize("@ss.hasAnyPermissions('system:user:add,system:user:edit')")
1
2
3
4
5

2、基于【角色标识】的权限控制

权限标识,对应 system_role 表的 code 字段, 例如说 super_admin 超级管理员、tenant_admin 租户管理员。

使用示例如下:

// 属于 user 角色
@PreAuthorize("@ss.hasRole('user')")

// 属于 user 或者 admin 之一
@PreAuthorize("@ss.hasAnyRoles('user', 'admin')")
1
2
3
4
5

@PreAuthorize 注解里的 Spring EL 表达式返回 false 时,表示没有权限 。若没有权限,则会触发 AccessDeniedHandler 的 handle 方法 。

public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
            throws IOException, ServletException {
        // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
        log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
                SecurityFrameworkUtils.getLoginUserId(), e);
        // 返回 403
        ServletUtils.writeJSON(response, CommonResult.error(FORBIDDEN));
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 3 实现原理

@PreAuthorize("@ss.hasPermission('system:user:list')") 表示调用 Bean 名字为 ss#hasPermission(...) 方法,方法参数为 "system:user:list" 字符串。

// 使用 Spring Security 的缩写,方便使用
@Bean("ss") 
public SecurityFrameworkService securityFrameworkService(PermissionAdapter permissionAdapter) {
     return new SecurityFrameworkServiceImpl(permissionAdapter);
}
1
2
3
4
5

而 SecurityFrameworkService 提供了多个权限校验的接口:

/**
 * Security 框架 Service 接口,定义权限相关的校验操作
 */
public interface SecurityFrameworkService {

    /**
     * 判断是否有权限
     *
     * @param permission 权限
     * @return 是否
     */
    boolean hasPermission(String permission);

    /**
     * 判断是否有权限,任一一个即可
     *
     * @param permissions 权限
     * @return 是否
     */
    boolean hasAnyPermissions(String... permissions);

    /**
     * 判断是否有角色
     *
     * 注意,角色使用的是 SysRoleDO 的 code 标识
     *
     * @param role 角色
     * @return 是否
     */
    boolean hasRole(String role);

    /**
     * 判断是否有角色,任一一个即可
     *
     * @param roles 角色数组
     * @return 是否
     */
    boolean hasAnyRoles(String... roles);

    /**
     * 判断是否有授权
     *
     * @param scope 授权
     * @return 是否
     */
    boolean hasScope(String scope);

    /**
     * 判断是否有授权范围,任一一个即可
     *
     * @param scope 授权范围数组
     * @return 是否
     */
    boolean hasAnyScopes(String... scope);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

我们重点关注 hasPermission 实现方法。

1、获取当前用户 ID

在每次请求时候,每次都会从请求头中获取 token 数据,然后根据 token 从 Redis 中获取登录用户信息:

{
    "accessToken":"d5516561a3124475a2b622eb0c8031e6",
    "refreshToken":"707ba1b4fbed4280a1ce04ca429fa379",
    "userId":1,
    "userType":2,
    "clientId":1,
    "expiresTime":1753954785960
}
1
2
3
4
5
6
7
8

2、通过用户 ID 、权限值判断用户是否具备该权限

    public boolean hasAnyPermissions(Long userId, String... permissions) {
        // 如果为空,说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 获得当前登录的角色。如果为空,说明没有权限
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        // 情况一:遍历判断每个权限,如果有一满足,说明有权限
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission)) {
                return true;
            }
        }

        // 情况二:如果是超管,也说明有权限
        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 查询用户所有的角色 ;
  2. 遍历权限数组,判断角色列表是否包含权限条目 ;
  3. 超级管理员拥有所有权限,避免权限配置遗漏。

RBAC 权限模型 参考文章:https://www.woshipm.com/pd/440765.html

上次更新: 2026/6/28