Featured image of post rbac是怎么写的?

rbac是怎么写的?

本文详细介绍了基于RBAC的权限管理系统实现方案。核心包括:数据库设计采用五张表(用户、角色、权限及关联表)构建"用户-角色-权限-服务器"四者关系;服务端通过SpringSecurity整合JWT实现无状态认证,支持动态权限加载与版本控制;前端基于权限数据动态渲染菜单和按钮。系统通过服务器-角色关联表实现精细化权限控制,解决了权限缓存、JWT版本同步等关键问题,最终实现了不同账户管理不同服务器的灵活权限管理机制。

​​一、数据库设计:理清角色-权限-用户的关系​​

RBAC 的核心是三张核心表 + 两张关联表,具体如下(简化版):

表名

作用

sys_user

用户表(存储账号、密码、状态等)

sys_role

角色表(比如「系统管理员」「普通运维」「只读用户」)

sys_permission

权限表(具体操作权限,比如「server:manage」「monitor:view」)

sys_user_role

用户-角色关联表(一个用户可有多个角色)

sys_role_perm

角色-权限关联表(一个角色可拥有多个权限)

sys_server

服务器表(存储被管理的服务器信息,比如IP、名称)

sys_server_role

服务器-角色关联表(一个服务器可分配给多个角色,实现「某服务器由某角色管理」)

举个例子:

  • 用户A的角色是「运维组」,「运维组」角色拥有「server:manage」权限;

  • 服务器1被分配了「运维组」角色,所以用户A能管理服务器1;

  • 服务器2被分配了「管理员」角色,用户A没有「管理员」角色,所以管不了服务器2。


​​二、服务端实现:SpringSecurity 整合 JWT + RBAC​​

我们用 SpringSecurity 做权限校验,结合 JWT 实现无状态认证,核心步骤如下:

1. ​​自定义用户认证(UserDetailsService)​​

用户登录时,前端传账号密码,服务端通过 UserDetailsService 加载用户信息(包括角色、权限)。这里需要从数据库查用户,再查关联的角色和权限,封装成 UserDetails 对象返回。

@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private SysUserMapper userMapper; // MyBatis-Plus 用户表Mapper

@Override
public UserDetails loadUserByUsername(String username) {
    // 1. 查用户是否存在
    SysUser user = userMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUsername, username));
    if (user == null) {
        throw new UsernameNotFoundException("用户不存在");
    }

    // 2. 查用户的所有角色(通过 sys_user_role 关联表)
    List<SysRole> roles = roleMapper.selectRolesByUserId(user.getId());

    // 3. 查角色对应的所有权限(通过 sys_role_perm 关联表)
    List<String> perms = roles.stream()
            .flatMap(role -> permMapper.selectPermsByRoleId(role.getId()).stream())
            .collect(Collectors.toList());

    // 4. 封装成 SpringSecurity 的 UserDetails(包含权限)
    return new CustomUserDetails(
        user.getUsername(), 
        user.getPassword(), 
        user.getStatus() == 1, // 是否启用
        true, true, true, 
        Collections.emptyList(), // 权限集合(这里用字符串列表,实际可用 Permission 对象)
        roles, 
        perms
    );
}

}

2. ​​权限拦截与校验​​

通过 @PreAuthorize 注解或自定义拦截器,校验用户是否有权限访问某个接口或操作。比如:

@RestController
@RequestMapping("/server")
public class ServerController {

// 只有拥有 'server:manage' 权限的用户才能访问
@PreAuthorize("hasAuthority('server:manage')")
@PostMapping("/add")
public Result addServer(@RequestBody Server server) {
    // 添加服务器逻辑
}

// 拥有 'server:view' 权限的用户都能查看
@PreAuthorize("hasAuthority('server:view')")
@GetMapping("/list")
public Result listServers() {
    // 查询服务器列表
}

}

3. ​​动态权限加载(解决角色/权限变更后缓存问题)​​

因为用了 JWT(无状态),用户权限变更后,旧 JWT 仍然有效,可能导致权限未及时更新。我们的解决方法是:

  • sys_user 表加 version 字段(每次修改用户权限时 version+1);

  • JWT 中携带 version 信息;

  • 每次请求时,服务端校验 JWT 中的 version 是否与数据库一致,不一致则拒绝请求并让用户重新登录。

// 自定义 JWT 校验过滤器(关键逻辑)
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
    String token = extractToken(request);
    if (token != null) {
        try {
            // 解析 JWT 得到用户信息和 version
            Claims claims = jwtUtil.parseToken(token);
            String username = claims.getSubject();
            Integer jwtVersion = claims.get("version", Integer.class);

            // 查数据库用户的当前 version
            SysUser user = userMapper.selectByUsername(username);
            if (user.getVersion() == null || !user.getVersion().equals(jwtVersion)) {
                // 版本不一致,权限可能变更,拒绝请求
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                return;
            }

            // 校验通过,生成 UserDetails 并设置到 SecurityContext
            UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities()
            );
            SecurityContextHolder.getContext().setAuthentication(auth);
            filterChain.doFilter(request, response);
        } catch (JwtException e) {
            // Token 无效,返回未授权
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
        }
    } else {
        filterChain.doFilter(request, response);
    }
}

}

4. ​​服务器-角色关联的权限控制​​

除了用户角色的权限,还要控制「用户是否能管理某台具体服务器」。比如:

  • 用户A有「运维组」角色,该角色被分配了服务器1和服务器2;

  • 用户A访问服务器3的管理接口时,需要校验「运维组」是否拥有服务器3的管理权限。

这部分在服务端接口中额外处理:

@PostMapping("/operate/{serverId}")
public Result operateServer(@PathVariable Long serverId, @RequestBody OperateReq req) {
// 1. 当前用户
String username = SecurityContextHolder.getContext().getAuthentication().getName();
// 2. 查用户拥有的角色
List<SysRole> userRoles = roleService.getUserRoles(username);
// 3. 查这些角色是否被分配了当前服务器
boolean hasPermission = serverRoleService.checkServerAssignedToRoles(serverId, userRoles);
if (!hasPermission) {
throw new AccessDeniedException(“无权限操作该服务器”);
}
// 4. 执行操作…
}

​​三、前端配合:动态菜单与按钮权限​​

前端(Vue3)需要根据用户权限动态渲染菜单和按钮,避免显示无权限的功能。具体步骤:

1. ​​登录后获取权限信息​​

用户登录成功后,后端返回用户的角色、权限列表(比如 [‘server:manage’, ‘monitor:view’]),前端存储到 Vuex 或 Pinia 中。

2. ​​动态生成菜单​​

菜单数据从后端获取(根据用户权限过滤),比如:

  • 管理员看到「服务器管理」「监控看板」「用户管理」;

  • 普通运维只看到「服务器管理」「监控看板」。

// Vue 组件中获取菜单
async function getMenus() {
const res = await axios.get(’/api/menus’, {
headers: { Authorization: Bearer ${token} }
});
// 根据用户权限过滤菜单(后端已处理,前端直接渲染)
store.commit(‘setMenus’, res.data);
}

3. ​​按钮级权限控制​​

通过自定义指令 v-permission 控制按钮是否显示:

// 注册全局指令
app.directive(‘permission’, {
mounted(el, binding) {
const perms = store.state.user.permissions; // 用户权限列表
const requiredPerm = binding.value; // 需要的权限(如 ‘server:manage’)
if (!perms.includes(requiredPerm)) {
el.parentNode?.removeChild(el); // 无权限则移除按钮
}
}
});

// 使用示例 <button v-permission="‘server:manage’">删除服务器</button>


​​四、关键细节与踩坑​​

  1. ​权限缓存​​:用户权限信息存在 Redis 中(键:user:perm:${username}),避免每次请求都查数据库。用户登出或权限变更时,删除对应缓存。

  2. ​JWT 与 RBAC 结合​​:JWT 中除了用户信息,还要存角色/权限的摘要(比如角色ID列表),避免每次请求都查数据库(但最终校验还是以数据库为准)。

  3. ​动态路由​​:前端路由需要根据权限动态添加(比如用 router.addRoute()),避免无权限的路由被访问。

  4. ​服务器-角色关联的灵活性​​:通过 sys_server_role 表实现「多对多」关系,一个服务器可分配给多个角色,一个角色可管理多个服务器,满足复杂权限需求。


总结来说, RBAC 实现围绕「用户-角色-权限-服务器」四者关系,通过 SpringSecurity 做权限校验,JWT 做无状态认证,前端动态渲染,最终实现了「不同账户管理不同服务器」的灵活权限控制。

最后更新于 2025-06-29 17:07 UTC