Skip to content
清晨的一缕阳光
返回

Spring Boot RBAC 权限控制

前言

RBAC(Role-Based Access Control)是基于角色的访问控制模型,广泛应用于企业权限管理系统。本文将介绍 Spring Boot 实现 RBAC 权限控制的完整方案。

数据库设计

1. 表结构

-- 用户表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    email VARCHAR(100),
    phone VARCHAR(20),
    status TINYINT DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL UNIQUE,
    code VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(200),
    status TINYINT DEFAULT 1
);

-- 权限表
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    code VARCHAR(100) NOT NULL UNIQUE,
    type TINYINT NOT NULL, -- 1:菜单 2:按钮 3:接口
    parent_id BIGINT DEFAULT 0,
    path VARCHAR(200),
    method VARCHAR(10),
    status TINYINT DEFAULT 1
);

-- 用户角色关联表
CREATE TABLE sys_user_role (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id)
);

-- 角色权限关联表
CREATE TABLE sys_role_permission (
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    PRIMARY KEY (role_id, permission_id)
);

2. 实体类

@Data
@Entity
@Table(name = "sys_user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String password;
    private String email;
    private String phone;
    private Integer status;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "sys_user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private List<Role> roles = new ArrayList<>();
}

@Data
@Entity
@Table(name = "sys_role")
public class Role {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String code;
    private String description;
    private Integer status;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "sys_role_permission",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private List<Permission> permissions = new ArrayList<>();
}

@Data
@Entity
@Table(name = "sys_permission")
public class Permission {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String code;
    private Integer type;
    private Long parentId;
    private String path;
    private String method;
    private Integer status;
}

用户详情服务

1. 实现 UserDetailsService

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
    
    private final UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) 
            throws UsernameNotFoundException {
        
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException(
                "用户不存在:" + username
            ));
        
        if (user.getStatus() != 1) {
            throw new DisabledException("用户已禁用");
        }
        
        List<GrantedAuthority> authorities = getAuthorities(user);
        
        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())
            .authorities(authorities)
            .disabled(user.getStatus() != 1)
            .build();
    }
    
    private List<GrantedAuthority> getAuthorities(User user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        
        // 添加角色
        for (Role role : user.getRoles()) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
            
            // 添加权限
            for (Permission permission : role.getPermissions()) {
                if (permission.getStatus() == 1) {
                    authorities.add(new SimpleGrantedAuthority(permission.getCode()));
                }
            }
        }
        
        return authorities;
    }
}

2. 自定义 UserPrincipal

@Data
@AllArgsConstructor
public class UserPrincipal implements UserDetails {
    
    private Long id;
    private String username;
    private String password;
    private List<GrantedAuthority> authorities;
    private boolean enabled;
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

动态权限控制

1. 权限元数据

@Data
public class PermissionMeta {
    
    private Long id;
    private String code;
    private String name;
    private Integer type; // 1:菜单 2:按钮 3:接口
    private String path;
    private String method;
    private Long parentId;
    private List<PermissionMeta> children;
}

2. 权限服务

@Service
@RequiredArgsConstructor
public class PermissionService {
    
    private final PermissionRepository permissionRepository;
    
    /**
     * 获取用户权限列表
     */
    public List<String> getUserPermissions(Long userId) {
        return permissionRepository.findUserPermissions(userId);
    }
    
    /**
     * 获取用户角色列表
     */
    public List<String> getUserRoles(Long userId) {
        return permissionRepository.findUserRoles(userId);
    }
    
    /**
     * 获取所有权限树
     */
    public List<PermissionMeta> getPermissionTree() {
        List<Permission> all = permissionRepository.findAll();
        return buildTree(all, 0L);
    }
    
    /**
     * 获取用户权限树
     */
    public List<PermissionMeta> getUserPermissionTree(Long userId) {
        List<String> codes = getUserPermissions(userId);
        List<Permission> permissions = 
            permissionRepository.findByCodeIn(codes);
        return buildTree(permissions, 0L);
    }
    
    private List<PermissionMeta> buildTree(List<Permission> permissions, Long parentId) {
        return permissions.stream()
            .filter(p -> Objects.equals(p.getParentId(), parentId))
            .map(p -> {
                PermissionMeta meta = new PermissionMeta();
                meta.setId(p.getId());
                meta.setCode(p.getCode());
                meta.setName(p.getName());
                meta.setType(p.getType());
                meta.setPath(p.getPath());
                meta.setMethod(p.getMethod());
                meta.setParentId(p.getParentId());
                meta.setChildren(buildTree(permissions, p.getId()));
                return meta;
            })
            .collect(Collectors.toList());
    }
}

3. 动态授权管理器

@Component
public class DynamicAuthorizationManager implements AuthorizationManager<RequestAttributeHolder> {
    
    private final PermissionService permissionService;
    
    public DynamicAuthorizationManager(PermissionService permissionService) {
        this.permissionService = permissionService;
    }
    
    @Override
    public AuthorizationDecision check(
        Supplier<Authentication> authentication,
        RequestAttributeHolder object
    ) {
        Authentication auth = authentication.get();
        if (auth == null || !auth.isAuthenticated()) {
            return new AuthorizationDecision(false);
        }
        
        HttpServletRequest request = object.getRequest();
        String uri = request.getRequestURI();
        String method = request.getMethod();
        
        // 获取用户权限
        UserPrincipal principal = (UserPrincipal) auth.getPrincipal();
        List<String> permissions = permissionService.getUserPermissions(principal.getId());
        
        // 检查是否有权限
        boolean hasPermission = permissions.stream()
            .anyMatch(code -> matchPermission(code, uri, method));
        
        return new AuthorizationDecision(hasPermission);
    }
    
    private boolean matchPermission(String permissionCode, String uri, String method) {
        // 权限码格式:method:path
        String[] parts = permissionCode.split(":");
        if (parts.length != 2) {
            return false;
        }
        
        String permMethod = parts[0];
        String permPath = parts[1];
        
        return permMethod.equalsIgnoreCase(method) && 
               pathMatcher.match(permPath, uri);
    }
}

4. 配置动态授权

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    
    private final DynamicAuthorizationManager dynamicAuthorizationManager;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/api/auth/**", "/api/public/**").permitAll()
                .anyRequest().access(dynamicAuthorizationManager)
            );
        
        return http.build();
    }
}

方法级别授权

1. 启用方法安全

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

2. 使用注解

@Service
public class UserService {
    
    /**
     * 需要角色
     */
    @PreAuthorize("hasRole('ADMIN')")
    public List<UserDTO> getAllUsers() {
        return userRepository.findAll();
    }
    
    /**
     * 需要权限
     */
    @PreAuthorize("hasAuthority('user:create')")
    public UserDTO createUser(UserCreateDTO dto) {
        return userRepository.save(convert(dto));
    }
    
    /**
     * 基于数据授权
     */
    @PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
    public UserDTO getUserById(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }
    
    /**
     * 自定义表达式
     */
    @PreAuthorize("@permissionService.hasPermission(#userId, 'user:read')")
    public UserDTO getUser(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }
}

3. 自定义权限评估器

@Component("permissionService")
public class PermissionEvaluator {
    
    private final PermissionService permissionService;
    
    public PermissionEvaluator(PermissionService permissionService) {
        this.permissionService = permissionService;
    }
    
    @PreAuthorize("hasRole('ADMIN')")
    public boolean hasPermission(Long userId, String permissionCode) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null) {
            return false;
        }
        
        UserPrincipal principal = (UserPrincipal) auth.getPrincipal();
        
        // 管理员拥有所有权限
        if (principal.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
            return true;
        }
        
        // 检查用户权限
        List<String> permissions = permissionService.getUserPermissions(principal.getId());
        return permissions.contains(permissionCode);
    }
}

数据权限

1. 数据权限注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
    
    /**
     * 部门 ID
     */
    String deptId() default "";
    
    /**
     * 用户 ID
     */
    String userId() default "";
}

2. 数据权限切面

@Aspect
@Component
@Order(1)
public class DataPermissionAspect {
    
    @Around("@annotation(dataPermission)")
    public Object around(ProceedingJoinPoint pjp, DataPermission dataPermission) throws Throwable {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        UserPrincipal principal = (UserPrincipal) auth.getPrincipal();
        
        // 获取用户数据权限
        DataScope dataScope = getDataScope(principal.getId());
        
        // 添加过滤条件
        DataScopeContext.set(dataScope);
        
        try {
            return pjp.proceed();
        } finally {
            DataScopeContext.clear();
        }
    }
    
    private DataScope getDataScope(Long userId) {
        // 从数据库获取用户的数据权限范围
        return dataScopeRepository.findByUserId(userId);
    }
}

3. 数据范围

@Data
public class DataScope {
    
    /**
     * 范围类型
     * 1: 全部数据
     * 2: 本部门及以下
     * 3: 本部门
     * 4: 仅本人
     * 5: 自定义
     */
    private Integer scopeType;
    
    /**
     * 自定义部门 ID 列表
     */
    private List<Long> deptIds;
}

4. MyBatis 拦截器

@Intercepts({
    @Signature(type = Executor.class, method = "query", 
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Component
public class DataPermissionInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        DataScope dataScope = DataScopeContext.get();
        if (dataScope == null) {
            return invocation.proceed();
        }
        
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
        String sql = boundSql.getSql();
        
        // 添加数据权限过滤
        String filteredSql = addDataScopeFilter(sql, dataScope);
        
        // 替换 SQL
        MetaObject metaObject = SystemMetaObject.forObject(boundSql);
        metaObject.setValue("sql", filteredSql);
        
        return invocation.proceed();
    }
    
    private String addDataScopeFilter(String sql, DataScope dataScope) {
        switch (dataScope.getScopeType()) {
            case 1: // 全部数据
                return sql;
            case 2: // 本部门及以下
                return sql + " AND dept_id IN (" + 
                    buildDeptSql(dataScope.getDeptIds()) + ")";
            case 3: // 本部门
                return sql + " AND dept_id = " + 
                    dataScope.getDeptIds().get(0);
            case 4: // 仅本人
                return sql + " AND create_by = " + 
                    SecurityContextHolder.getContext().getName();
            default:
                return sql;
        }
    }
}

最佳实践

1. 权限初始化

@Component
@RequiredArgsConstructor
public class PermissionInitializer implements ApplicationRunner {
    
    private final PermissionService permissionService;
    
    @Override
    public void run(ApplicationArguments args) {
        // 初始化权限数据
        permissionService.initPermissions();
    }
}

2. 缓存权限

@Service
@RequiredArgsConstructor
public class PermissionService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    @Cacheable(value = "permissions", key = "'user:' + #userId")
    public List<String> getUserPermissions(Long userId) {
        return permissionRepository.findUserPermissions(userId);
    }
    
    @CacheEvict(value = "permissions", key = "'user:' + #userId")
    public void clearUserPermissionCache(Long userId) {
        // 清除缓存
    }
}

3. 权限变更通知

@Service
public class PermissionChangeService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void notifyPermissionChange(Long userId) {
        eventPublisher.publishEvent(new PermissionChangeEvent(this, userId));
    }
}

总结

RBAC 权限控制要点:

RBAC 是企业权限管理的基础模型。


分享这篇文章到:

上一篇文章
RocketMQ 集成
下一篇文章
MySQL 主从复制原理