Java角色权限案例如何设计

wen java案例 28

本文目录导读:

Java角色权限案例如何设计

  1. 核心概念:RBAC 模型
  2. 数据库表设计(MySQL DDL 示例)
  3. 领域模型设计(Java 代码)
  4. 核心业务逻辑层
  5. 权限控制实现(结合 Spring)
  6. 前端配合
  7. 一个完整的实战案例流程
  8. 总结与扩展

设计一个Java角色权限案例,核心在于理解 RBAC(基于角色的访问控制) 模型,这是目前最主流、可扩展性最好的权限设计模型。

下面我将以一个 “企业内部管理系统” 为例,为你展示如何从0到1设计一个简洁但完整的角色权限案例,包含数据库设计、核心逻辑和代码示例。


核心概念:RBAC 模型

在开始编码前,先理清这几个核心概念的关系:

  1. 用户 (User):系统的具体操作者(张三、李四)。
  2. 角色 (Role):权限的集合,代表一种身份或职位(管理员、项目经理、普通员工)。
  3. 权限 (Permission):对某个资源(Resource)进行某种操作(Operation)的许可(“用户管理-新增”、“订单-查看”)。
  4. 资源 (Resource):系统操作的对象(用户、订单、报表)。

关系链(5张核心表): 用户 (User) <--多对多--> 用户角色关联表 <--多对多--> 角色 (Role) <--多对多--> 角色权限关联表 <--多对多--> 权限 (Permission)

一句话总结: 我们把权限赋予给角色,再把角色赋予给用户,用户最终拥有的权限 = 他所有角色的权限的并集


数据库表设计(MySQL DDL 示例)

这是整个案例的基石,我们采用 5张主要表 + 2张关联表 的设计。

-- 1. 用户表 sys_user
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    nickname VARCHAR(50),
    status TINYINT DEFAULT 1 COMMENT '1:启用 0:禁用',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 2. 角色表 sys_role
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL UNIQUE,
    role_key VARCHAR(50) NOT NULL UNIQUE COMMENT '角色标识,如 admin, manager',
    status TINYINT DEFAULT 1,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 3. 权限表 sys_permission
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    perm_name VARCHAR(50) NOT NULL COMMENT '权限名称,如 用户新增',
    perm_key VARCHAR(100) NOT NULL UNIQUE COMMENT '权限标识,如 sys:user:add',
    parent_id BIGINT DEFAULT 0 COMMENT '父权限ID,用于构建树形菜单',
    menu_name VARCHAR(50) COMMENT '菜单名称(如果是菜单级权限)',
    menu_url VARCHAR(200) COMMENT '菜单URL',
    perm_type ENUM('menu', 'button', 'api') DEFAULT 'api' COMMENT '权限类型: menu菜单, button按钮, api接口',
    status TINYINT DEFAULT 1,
    sort_order INT DEFAULT 0,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 4. 用户角色关联表 sys_user_role
-- 一个用户可以有多个角色,一个角色可以属于多个用户
CREATE TABLE sys_user_role (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id)
);
-- 5. 角色权限关联表 sys_role_permission
-- 一个角色可以有多个权限,一个权限可以属于多个角色
CREATE TABLE sys_role_permission (
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    PRIMARY KEY (role_id, permission_id)
);

设计要点:

  • perm_key 使用冒号分隔的层级格式(如 sys:user:add),方便 Spring Security 等框架匹配。
  • parent_idperm_type 用于构建树形菜单和区分按钮/API级别的控制。
  • 使用关联表而非直接在用户表里加角色字段,是为了支持 多角色 场景。

领域模型设计(Java 代码)

// User.java
public class User {
    private Long id;
    private String username;
    private String password;
    // 临时存放当前用户拥有的角色和权限 (从DB查询后组装)
    private List<Role> roles;
    private Set<String> permissions; // 存放 perm_key 集合, 如 ["sys:user:add", "sys:order:view"]
    // getters and setters...
}
// Role.java
public class Role {
    private Long id;
    private String roleName;
    private String roleKey;
    private List<Permission> permissions;
    // getters and setters...
}
// Permission.java
public class Permission {
    private Long id;
    private String permName;
    private String permKey;
    private Long parentId;
    private String menuName;
    private String menuUrl;
    private String permType; // "menu", "button", "api"
    // getters and setters...
}

核心业务逻辑层

登录与授权(核心方法)

通常在 UserServiceAuthService 中实现。

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    /**
     * 根据用户名获取用户及其完整权限信息。
     * 前端登录成功后,后端将此对象放入Session或生成JWT Token返回。
     */
    public User getUserWithPermissions(String username) {
        // 1. 查询用户基本信息
        User user = userMapper.findByUsername(username);
        if (user == null) return null;
        // 2. 查询用户的所有角色
        List<Role> roles = roleMapper.findRolesByUserId(user.getId());
        user.setRoles(roles);
        // 3. 查询用户所有角色的所有权限(去重)
        Set<String> permissions = new HashSet<>();
        for (Role role : roles) {
            List<Permission> perms = permissionMapper.findPermissionsByRoleId(role.getId());
            for (Permission perm : perms) {
                // 只收集api和button类型的权限标识进行控制
                if (!"menu".equals(perm.getPermType())) {
                    permissions.add(perm.getPermKey());
                }
            }
        }
        user.setPermissions(permissions);
        return user;
    }
}

权限检查工具(核心判断逻辑)

/**
 * 在当前线程请求中检查用户是否有权限。
 * 一般配合 Spring AOP 或 Filter 使用。
 */
@Service
public class PermissionCheckService {
    // 模拟从 ThreadLocal 或 SecurityContextHolder 获取当前用户
    public boolean hasPermission(String requiredPermKey) {
        // 1. 获取当前登录用户
        User currentUser = getCurrentUser();
        if (currentUser == null) return false;
        // 2. 检查用户的权限集合是否包含所需权限
        boolean has = currentUser.getPermissions().contains(requiredPermKey);
        System.out.println("检查权限 [" + requiredPermKey + "] 结果: " + has);
        return has;
    }
}

权限控制实现(结合 Spring)

方案A:基于注解(推荐,最优雅)

// 1. 自定义权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String value(); // 权限标识, 如 "sys:user:add"
}
// 2. 使用 Spring AOP 实现拦截逻辑
@Aspect
@Component
public class PermissionAspect {
    @Autowired
    private PermissionCheckService permissionCheckService;
    // 拦截所有带有 @RequirePermission 注解的方法
    @Around("@annotation(requirePermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, RequirePermission requirePermission) throws Throwable {
        String requiredPerm = requirePermission.value();
        // 调用核心检查逻辑
        if (permissionCheckService.hasPermission(requiredPerm)) {
            return joinPoint.proceed(); // 有权限,继续执行
        } else {
            // 无权限,抛出异常或返回错误信息
            throw new RuntimeException("No Permission: " + requiredPerm);
        }
    }
}
// 3. 在Controller中使用
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    @RequirePermission("sys:user:add")
    public ResponseEntity<String> addUser(@RequestBody User user) {
        // 只有拥有 sys:user:add 权限的用户才能访问
        return ResponseEntity.ok("用户创建成功");
    }
    @GetMapping
    @RequirePermission("sys:user:list")
    public ResponseEntity<List<User>> listUsers() {
        // 只有拥有 sys:user:list 权限的用户才能访问
        return ResponseEntity.ok(userService.findAll());
    }
}

方案B:基于 Filter/Interceptor

在 Spring Security 或自定义 Filter 中,对请求的URL与权限进行匹配。

// 伪代码
@WebFilter(urlPatterns = "/api/*")
public class PermissionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // 1. 解析请求URL,判断是否受保护的资源(/api/users/add)
        String requestURI = extractUrl(request);
        // 2. 根据请求方法+URL 查询数据库,找到需要的权限标识(sys:user:add)
        String requiredPermKey = getRequiredPermFromUrl(requestURI, request.getMethod());
        if (requiredPermKey == null) {
            // 该URL不需要权限,直接放行
            chain.doFilter(request, response);
        } else {
            // 3. 调用核心检查逻辑
            if (permissionCheckService.hasPermission(requiredPermKey)) {
                chain.doFilter(request, response);
            } else {
                // 4. 无权限,返回403
                ((HttpServletResponse) response).sendError(403, "无权限访问");
            }
        }
    }
}

前端配合

  1. 登录后获取权限:用户登录成功后,后端返回 user.permissions(权限标识集合)。
  2. 菜单动态生成:前端根据 user.rolesuser.permissions 过滤菜单列表(只显示有权限的菜单)。
  3. 按钮级显隐:在模板中使用类似 v-if="hasPermission('sys:user:add')" 的指令来控制新增按钮的显示与隐藏。

一个完整的实战案例流程

假设有一个 “订单管理系统”

  1. 管理员:拥有 order:create, order:view, order:edit, order:delete 所有权限。
  2. 客服人员:只拥有 order:view, order:edit 权限。
  3. 实习生:只拥有 order:view 权限。

操作验证:

  • 实习生 登录后,前端订单列表的“新增”、“编辑”、“删除”按钮都不可见;通过API直接调用创建接口会返回403。
  • 客服人员 登录后,可以看到“编辑”按钮,但看不到“新增”和“删除”按钮。
  • 管理员 可以看到所有按钮,并能操作所有功能。

总结与扩展

这是一个经典的、开箱即用的RBAC案例。 它给出了清晰的数据库结构、Java实体、权限检查逻辑和Spring集成方式。

进阶扩展点(当你需要更复杂的场景时):

  1. 数据权限:除了功能权限,可能还需要控制数据行,客服A只能看自己的客户订单,经理能看所有,这需要在SQL中根据用户角色ID或部门ID添加额外的 WHERE 条件。
  2. 缓存:用户权限通常是低频变动的,可以将用户的角色和权限信息缓存到Redis中,提升性能。
  3. Spring Security集成:上述AOP方案的理念与Spring Security的 @PreAuthorize 注解高度一致,你可以直接使用Spring Security提供的 hasPermission 表达式,它更成熟和完善。

这个设计案例可以很好地满足中小型系统的权限管理需求。

抱歉,评论功能暂时关闭!