RBAC案例

wen java案例 4

本文目录导读:

RBAC案例

  1. 业务场景
  2. 数据库设计
  3. Java代码实现
  4. MyBatis Mapper示例
  5. 前端按钮权限控制
  6. 测试用例
  7. 性能优化建议

我来为你提供一个完整的RBAC(基于角色的访问控制)案例,从需求分析到代码实现。

业务场景

假设我们要为一个企业管理系统设计RBAC权限模型,包含用户管理、项目管理、任务管理三个核心模块。

数据库设计

基础表结构

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    email VARCHAR(100),
    status TINYINT DEFAULT 1 COMMENT '1:启用,0:禁用',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL UNIQUE,
    role_code VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(200),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 权限表
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    perm_name VARCHAR(50) NOT NULL,
    perm_code VARCHAR(50) NOT NULL UNIQUE,
    module VARCHAR(50) COMMENT '所属模块',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 用户-角色关联表
CREATE TABLE user_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    UNIQUE KEY uk_user_role (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (role_id) REFERENCES roles(id)
);
-- 角色-权限关联表
CREATE TABLE role_permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    UNIQUE KEY uk_role_perm (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(id),
    FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

初始化数据

-- 插入权限数据
INSERT INTO permissions (perm_name, perm_code, module) VALUES
-- 用户管理模块
('查询用户', 'user:list', 'user'),
('创建用户', 'user:create', 'user'),
('编辑用户', 'user:edit', 'user'),
('删除用户', 'user:delete', 'user'),
-- 项目管理模块
('查看项目', 'project:view', 'project'),
('创建项目', 'project:create', 'project'),
('编辑项目', 'project:edit', 'project'),
('删除项目', 'project:delete', 'project'),
-- 任务管理模块
('查看任务', 'task:view', 'task'),
('创建任务', 'task:create', 'task'),
('编辑任务', 'task:edit', 'task'),
('删除任务', 'task:delete', 'task'),
('分配任务', 'task:assign', 'task');
-- 插入角色数据
INSERT INTO roles (role_name, role_code, description) VALUES
('系统管理员', 'ADMIN', '拥有所有权限'),
('项目经理', 'PROJECT_MANAGER', '管理项目和任务'),
('开发人员', 'DEV', '处理任务'),
('访客', 'GUEST', '只读权限');
-- 分配角色权限
-- 管理员拥有所有权限
INSERT INTO role_permissions (role_id, permission_id)
SELECT 1, id FROM permissions;
-- 项目经理(查看项目、创建项目、编辑项目、删除项目、查看任务、创建任务、分配任务)
INSERT INTO role_permissions (role_id, permission_id) VALUES
(2, 5), (2, 6), (2, 7), (2, 8), (2, 9), (2, 10), (2, 13);
-- 开发人员(查看任务、编辑任务)
INSERT INTO role_permissions (role_id, permission_id) VALUES
(3, 9), (3, 11);
-- 访客(查看项目、查看任务)
INSERT INTO role_permissions (role_id, permission_id) VALUES
(4, 5), (4, 9);

Java代码实现

权限注解定义

package com.example.rbac.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
    String value();
}

核心实体类

package com.example.rbac.entity;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Set;
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String email;
    private Integer status;
    private LocalDateTime createdAt;
    private Set<Role> roles;
}
@Data
public class Role {
    private Long id;
    private String roleName;
    private String roleCode;
    private String description;
    private Set<Permission> permissions;
}
@Data
public class Permission {
    private Long id;
    private String permName;
    private String permCode;
    private String module;
}

权限服务层

package com.example.rbac.service;
import com.example.rbac.entity.Permission;
import com.example.rbac.entity.Role;
import com.example.rbac.entity.User;
public interface PermissionService {
    /**
     * 获取用户所有权限
     */
    Set<String> getUserPermissions(Long userId);
    /**
     * 验证用户是否拥有指定权限
     */
    boolean hasPermission(Long userId, String permissionCode);
    /**
     * 获取用户角色列表
     */
    Set<Role> getUserRoles(Long userId);
}
@Service
public class PermissionServiceImpl implements PermissionService {
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private RolePermissionMapper rolePermissionMapper;
    @Override
    public Set<String> getUserPermissions(Long userId) {
        // 使用缓存提升性能
        String cacheKey = "user:permissions:" + userId;
        Set<String> permissions = redisUtil.get(cacheKey);
        if (permissions == null) {
            // 查询用户角色
            List<Role> roles = userRoleMapper.findRolesByUserId(userId);
            // 通过角色获取权限
            Set<String> permCodes = new HashSet<>();
            for (Role role : roles) {
                List<Permission> permissions = rolePermissionMapper.findPermissionsByRoleId(role.getId());
                permissions.forEach(perm -> permCodes.add(perm.getPermCode()));
            }
            permissions = permCodes;
            // 缓存10分钟
            redisUtil.set(cacheKey, permissions, 600);
        }
        return permissions;
    }
    @Override
    public boolean hasPermission(Long userId, String permissionCode) {
        Set<String> permissions = getUserPermissions(userId);
        return permissions.contains(permissionCode);
    }
}

自定义权限拦截器

package com.example.rbac.interceptor;
import com.example.rbac.annotation.RequiresPermission;
import com.example.rbac.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class PermissionInterceptor implements HandlerInterceptor {
    @Autowired
    private PermissionService permissionService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        // 如果不是映射到方法,直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        RequiresPermission requiresPermission = handlerMethod.getMethodAnnotation(RequiresPermission.class);
        if (requiresPermission == null) {
            return true;
        }
        // 获取当前登录用户ID(这里假设从Session中获取)
        Long userId = (Long) request.getSession().getAttribute("userId");
        if (userId == null) {
            response.setStatus(401);
            return false;
        }
        // 验证权限
        if (!permissionService.hasPermission(userId, requiresPermission.value())) {
            response.setStatus(403);
            response.setContentType("application/json");
            response.getWriter().write("{\"code\":403,\"message\":\"权限不足\"}");
            return false;
        }
        return true;
    }
}

控制器示例

package com.example.rbac.controller;
import com.example.rbac.annotation.RequiresPermission;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/projects")
public class ProjectController {
    @GetMapping
    @RequiresPermission("project:view")
    public Result listProjects() {
        // 查看项目列表
        return Result.success();
    }
    @PostMapping
    @RequiresPermission("project:create")
    public Result createProject(@RequestBody ProjectDTO projectDTO) {
        // 创建项目
        return Result.success();
    }
    @PutMapping("/{id}")
    @RequiresPermission("project:edit")
    public Result editProject(@PathVariable Long id, @RequestBody ProjectDTO projectDTO) {
        // 编辑项目
        return Result.success();
    }
    @DeleteMapping("/{id}")
    @RequiresPermission("project:delete")
    public Result deleteProject(@PathVariable Long id) {
        // 删除项目
        return Result.success();
    }
}

动态菜单和按钮控制

package com.example.rbac.controller;
@RestController
@RequestMapping("/api/menu")
public class MenuController {
    @Autowired
    private PermissionService permissionService;
    @GetMapping("/user")
    public Result getUserMenus(Long userId) {
        // 获取用户所有权限码
        Set<String> permissions = permissionService.getUserPermissions(userId);
        // 构建菜单树
        List<MenuVO> menus = new ArrayList<>();
        // 根据权限动态生成菜单
        if (permissions.contains("user:list")) {
            menus.add(new MenuVO("用户管理", "/user/list", "user"));
        }
        if (permissions.contains("project:view")) {
            menus.add(new MenuVO("项目管理", "/project/list", "project"));
        }
        if (permissions.contains("task:view")) {
            menus.add(new MenuVO("任务管理", "/task/list", "task"));
        }
        return Result.success(menus);
    }
}

MyBatis Mapper示例

@Mapper
public interface UserRoleMapper {
    @Select("SELECT r.* FROM roles r " +
            "INNER JOIN user_roles ur ON r.id = ur.role_id " +
            "WHERE ur.user_id = #{userId}")
    List<Role> findRolesByUserId(Long userId);
    @Insert("INSERT INTO user_roles (user_id, role_id) VALUES (#{userId}, #{roleId})")
    int insertUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);
    @Delete("DELETE FROM user_roles WHERE user_id = #{userId} AND role_id = #{roleId}")
    int deleteUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);
}
@Mapper
public interface RolePermissionMapper {
    @Select("SELECT p.* FROM permissions p " +
            "INNER JOIN role_permissions rp ON p.id = rp.permission_id " +
            "WHERE rp.role_id = #{roleId}")
    List<Permission> findPermissionsByRoleId(Long roleId);
    @Insert("INSERT INTO role_permissions (role_id, permission_id) VALUES (#{roleId}, #{permissionId})")
    int insertRolePermission(@Param("roleId") Long roleId, @Param("permissionId") Long permissionId);
    @Delete("DELETE FROM role_permissions WHERE role_id = #{roleId}")
    int deleteRolePermissions(Long roleId);
}

前端按钮权限控制

// 权限指令定义
Vue.directive('permission', {
  bind(el, binding) {
    const requiredPermission = binding.value;
    const userPermissions = store.state.userPermissions || [];
    if (!userPermissions.includes(requiredPermission)) {
      el.parentNode.removeChild(el);
    }
  }
});
// 按钮控制示例
<el-button 
  v-permission="'project:delete'" 
  type="danger" 
  @click="deleteProject">
  删除项目
</el-button>
<el-button 
  v-if="checkPermission('task:assign')" 
  type="primary">
  分配任务
</el-button>

测试用例

@SpringBootTest
public class RbacTest {
    @Autowired
    private PermissionService permissionService;
    @Test
    public void testAdminPermissions() {
        // 假设用户1是管理员
        assertTrue(permissionService.hasPermission(1L, "user:create"));
        assertTrue(permissionService.hasPermission(1L, "project:delete"));
        assertTrue(permissionService.hasPermission(1L, "task:assign"));
    }
    @Test
    public void testDevPermissions() {
        // 假设用户3是开发人员
        assertTrue(permissionService.hasPermission(3L, "task:view"));
        assertTrue(permissionService.hasPermission(3L, "task:edit"));
        assertFalse(permissionService.hasPermission(3L, "project:delete"));
    }
    @Test
    public void testGuestPermissions() {
        // 假设用户4是访客
        assertTrue(permissionService.hasPermission(4L, "project:view"));
        assertTrue(permissionService.hasPermission(4L, "task:view"));
        assertFalse(permissionService.hasPermission(4L, "task:edit"));
        assertFalse(permissionService.hasPermission(4L, "user:create"));
    }
}

性能优化建议

// 1. 使用Redis缓存权限信息
public void clearPermissionCache(Long userId) {
    String cacheKey = "user:permissions:" + userId;
    redisUtil.delete(cacheKey);
}
// 2. 权限变更时清除缓存
@Transactional
public void updateRolePermissions(Long roleId, List<Long> permissionIds) {
    rolePermissionMapper.deleteRolePermissions(roleId);
    for (Long permissionId : permissionIds) {
        rolePermissionMapper.insertRolePermission(roleId, permissionId);
    }
    // 清除受影响用户的缓存
    List<Long> userIds = userMapper.findUsersByRoleId(roleId);
    userIds.forEach(this::clearPermissionCache);
}
// 3. 使用布隆过滤器快速过滤无权限请求
@Component
public class PermissionBloomFilter {
    private final BloomFilter<String> filter = BloomFilter.create(
        Funnels.stringFunnel(StandardCharsets.UTF_8), 100000, 0.01);
    public boolean mightContain(String permCode) {
        return filter.mightContain(permCode);
    }
    @Scheduled(initialDelay = 60000, fixedDelay = 86400000)
    public void refresh() {
        List<String> allPermCodes = permissionMapper.findAllPermCodes();
        allPermCodes.forEach(filter::put);
    }
}

这个RBAC案例涵盖了:

  1. 完整的数据库设计:用户、角色、权限三类核心表及关联表
  2. 灵活的权限控制:支持注解和动态API级别的权限校验
  3. 前端配合:按钮级别和菜单级别的权限控制
  4. 性能优化:基于Redis的缓存机制
  5. 完整的测试覆盖:针对不同角色的权限验证

RBAC模型的核心优势是松耦合易于扩展,新增角色只需要配置权限组合,不需要修改业务代码。

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