SpringBoot实现权限管理系统完整指南(附源码)
一、权限系统核心概念
1.1 权限模型对比
模型类型 | 特点 | 适用场景 |
---|---|---|
ACL | 直接定义用户-资源权限关系 | 简单系统、资源数量少 |
RBAC | 通过角色关联权限,用户关联角色 | 企业级应用(本系统采用) |
ABAC | 基于属性动态计算权限 | 复杂权限规则、云环境 |
1.2 RBAC核心元素
二、项目搭建与配置
2.1 初始化SpringBoot项目
Maven关键依赖:
<dependencies><!-- Spring Boot Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- 数据库 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- 工具类 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- JWT支持 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
</dependencies>
2.2 数据库设计
2.2.1 用户表
CREATE TABLE `sys_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码',`salt` varchar(20) DEFAULT NULL COMMENT '盐',`status` tinyint(4) DEFAULT '1' COMMENT '状态 0-禁用 1-正常',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2.2 权限表
CREATE TABLE `sys_permission` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(50) NOT NULL COMMENT '权限名称',`code` varchar(100) NOT NULL COMMENT '权限标识',`type` tinyint(4) NOT NULL COMMENT '类型 1-菜单 2-按钮 3-API',`url` varchar(200) DEFAULT NULL COMMENT '访问路径',`parent_id` bigint(20) DEFAULT NULL COMMENT '父级ID',`order_num` int(11) DEFAULT '0' COMMENT '排序',PRIMARY KEY (`id`),UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、核心实现代码
3.1 安全配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/auth/login").permitAll().antMatchers("/swagger**/**").permitAll().anyRequest().authenticated();// 添加JWT过滤器http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}
3.2 JWT工具类
public class JwtUtil {private static final String SECRET_KEY = "your-secret-key";private static final long EXPIRATION = 86400000; // 24小时public static String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)).signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();}public static String getUsernameFromToken(String token) {return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();}public static boolean validateToken(String token, UserDetails userDetails) {final String username = getUsernameFromToken(token);return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));}private static boolean isTokenExpired(String token) {final Date expiration = getExpirationDateFromToken(token);return expiration.before(new Date());}private static Date getExpirationDateFromToken(String token) {return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration();}
}
3.3 权限控制实现
3.3.1 自定义权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {String[] value();Logical logical() default Logical.AND;
}public enum Logical {AND, OR
}
3.3.2 权限验证切面
@Aspect
@Component
public class PermissionAspect {@Autowiredprivate PermissionService permissionService;@Before("@annotation(requiresPermission)")public void before(RequiresPermission requiresPermission) {String[] permissions = requiresPermission.value();Logical logical = requiresPermission.logical();// 获取当前用户权限Set<String> userPermissions = permissionService.getCurrentUserPermissions();if (logical == Logical.AND) {for (String permission : permissions) {if (!userPermissions.contains(permission)) {throw new AccessDeniedException("无权限访问");}}} else {boolean hasAny = false;for (String permission : permissions) {if (userPermissions.contains(permission)) {hasAny = true;break;}}if (!hasAny) {throw new AccessDeniedException("无权限访问");}}}
}
3.4 动态权限数据源
@Component
public class DynamicPermissionService implements FilterInvocationSecurityMetadataSource {@Autowiredprivate PermissionMapper permissionMapper;@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {String requestUrl = ((FilterInvocation) object).getRequestUrl();// 从数据库加载所有权限配置List<Permission> permissions = permissionMapper.findAll();for (Permission permission : permissions) {if (antPathMatcher.match(permission.getUrl(), requestUrl)) {return SecurityConfig.createList(permission.getCode());}}// 没有匹配到默认需要登录return SecurityConfig.createList("ROLE_LOGIN");}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}
}
四、业务功能实现
4.1 用户登录接口
@RestController
@RequestMapping("/auth")
public class AuthController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtUtil jwtUtil;@PostMapping("/login")public Result login(@RequestBody LoginRequest request) {try {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(),request.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);UserDetails userDetails = (UserDetails) authentication.getPrincipal();String token = jwtUtil.generateToken(userDetails);return Result.success(token);} catch (AuthenticationException e) {return Result.error("用户名或密码错误");}}
}
4.2 用户权限服务
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleMapper roleMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. 查询用户User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("用户不存在");}// 2. 查询角色和权限List<Role> roles = roleMapper.findByUserId(user.getId());List<GrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));for (Permission permission : role.getPermissions()) {authorities.add(new SimpleGrantedAuthority(permission.getCode()));}}return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.getStatus() == 1,true, true, true,authorities);}
}
五、前端接口示例
5.1 权限树形结构接口
@RestController
@RequestMapping("/api/permission")
public class PermissionController {@Autowiredprivate PermissionService permissionService;@GetMapping("/tree")@RequiresPermission("permission:view")public Result getPermissionTree() {List<PermissionNode> tree = permissionService.getPermissionTree();return Result.success(tree);}
}@Service
public class PermissionServiceImpl implements PermissionService {@Autowiredprivate PermissionMapper permissionMapper;@Overridepublic List<PermissionNode> getPermissionTree() {List<Permission> allPermissions = permissionMapper.findAll();// 构建树形结构return buildTree(allPermissions, 0L);}private List<PermissionNode> buildTree(List<Permission> permissions, Long parentId) {List<PermissionNode> nodes = new ArrayList<>();for (Permission permission : permissions) {if (permission.getParentId() != null && permission.getParentId().equals(parentId)) {PermissionNode node = new PermissionNode();BeanUtils.copyProperties(permission, node);node.setChildren(buildTree(permissions, permission.getId()));nodes.add(node);}}return nodes;}
}
5.2 角色分配权限接口
@PostMapping("/role/assign")
@RequiresPermission("role:assign")
public Result assignPermissions(@RequestBody RolePermissionRequest request) {roleService.assignPermissions(request.getRoleId(), request.getPermissionIds());return Result.success();
}@Service
@Transactional
public class RoleServiceImpl implements RoleService {@Autowiredprivate RolePermissionMapper rolePermissionMapper;@Overridepublic void assignPermissions(Long roleId, List<Long> permissionIds) {// 1. 删除原有权限rolePermissionMapper.deleteByRoleId(roleId);// 2. 添加新权限if (!CollectionUtils.isEmpty(permissionIds)) {List<RolePermission> rolePermissions = permissionIds.stream().map(permissionId -> {RolePermission rp = new RolePermission();rp.setRoleId(roleId);rp.setPermissionId(permissionId);return rp;}).collect(Collectors.toList());rolePermissionMapper.batchInsert(rolePermissions);}}
}
六、系统扩展与优化
6.1 权限缓存优化
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).entryTtl(Duration.ofHours(1));return RedisCacheManager.builder(factory).cacheDefaults(config).build();}
}@Service
public class PermissionServiceImpl implements PermissionService {@Cacheable(value = "userPermissions", key = "#userId")@Overridepublic Set<String> getUserPermissions(Long userId) {// 数据库查询逻辑}@CacheEvict(value = "userPermissions", key = "#userId")@Overridepublic void clearUserPermissionsCache(Long userId) {// 清除缓存}
}
6.2 操作日志切面
@Aspect
@Component
public class LogAspect {@Autowiredprivate LogService logService;@AfterReturning(pointcut = "@annotation(loggable)", returning = "result")public void afterReturning(JoinPoint joinPoint, Loggable loggable, Object result) {saveLog(joinPoint, loggable, null);}@AfterThrowing(pointcut = "@annotation(loggable)", throwing = "ex")public void afterThrowing(JoinPoint joinPoint, Loggable loggable, Exception ex) {saveLog(joinPoint, loggable, ex.getMessage());}private void saveLog(JoinPoint joinPoint, Loggable loggable, String errorMsg) {// 获取请求信息HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// 构造日志对象SysLog log = new SysLog();log.setOperation(loggable.value());log.setMethod(joinPoint.getSignature().getName());log.setParams(JsonUtils.toJson(joinPoint.getArgs()));log.setIp(IPUtils.getIpAddr(request));log.setStatus(errorMsg == null ? 1 : 0);log.setErrorMsg(errorMsg);// 异步保存日志logService.saveLog(log);}
}
七、项目部署与测试
7.1 测试用例示例
@SpringBootTest
public class AuthServiceTest {@Autowiredprivate AuthService authService;@Testpublic void testLoginSuccess() {LoginRequest request = new LoginRequest("admin", "123456");Result result = authService.login(request);assertTrue(result.isSuccess());assertNotNull(result.getData());}@Testpublic void testLoginFail() {LoginRequest request = new LoginRequest("admin", "wrong-password");Result result = authService.login(request);assertFalse(result.isSuccess());}
}
7.2 接口测试(Postman)
登录请求:
POST /auth/login
Content-Type: application/json{"username": "admin","password": "123456"
}
响应示例:
{"code": 200,"message": "success","data": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTYy..."
}
访问受保护接口:
GET /api/user/list
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
结语
本系统完整实现了基于SpringBoot的RBAC权限管理系统,具有以下特点:
- 采用JWT实现无状态认证
- 支持动态权限配置
- 精细化的权限控制(方法级+URL级)
- 完善的缓存机制
扩展建议:
- 增加多因素认证(短信/邮箱验证码)
- 实现权限变更实时生效(WebSocket推送)
- 添加权限变更历史记录
完整项目源码已托管至GitHub:项目地址
通过本系统,开发者可以快速构建安全可靠的企业级应用,并根据实际需求进行个性化扩展。