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

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 做无状态认证,前端动态渲染,最终实现了「不同账户管理不同服务器」的灵活权限控制。