在Web系统中,权限控制是确保系统安全性和数据完整性的重要组成部分。本文将详细介绍如何在SpringBoot项目中实现两种常见的权限控制模型:ACL(Access Control List)和RBAC(Role-Based Access Control)。通过本文,你将了解这两种权限控制模型的设计思路、表结构设计、代码实现以及如何在SpringBoot项目中应用它们。
序章:权限控制的基本概念
在后台管理系统中,权限控制是必不可少的。常见的权限控制模型有两种:
- ACL(Access Control List):基于控制列表的权限控制。每个权限(如Controller方法)都有一个用户列表,只有列表中的用户才能访问该权限。
- RBAC(Role-Based Access Control):基于角色的权限控制。用户通过角色与权限关联,角色拥有多个权限,用户通过成为角色的成员来获得权限。
ACL与RBAC的对比
特性 | ACL(访问控制列表) | RBAC(基于角色的访问控制) |
核心思想 | 用户直接与权限挂钩 | 用户通过角色与权限关联 |
优点 | 简单易用,开发便捷 | 简化用户和权限管理,易于扩展 |
缺点 | 权限管理分散,用户直接关联权限,管理复杂 | 开发相对复杂,需要设计角色和权限的关联 |
适用场景 | 简单的权限控制场景,如文件系统的读写权限 | 复杂的权限控制场景,如企业级管理系统 |
接下来,我们将分别介绍如何在SpringBoot项目中实现ACL和RBAC权限控制。
第一章:ACL权限控制
1.1 表结构设计
ACL的核心是用户直接与权限挂钩,因此我们需要设计三张表:
- 用户表(User):存储用户信息。
- 权限表(Permission):存储权限信息。
- ACL表(ACL):存储用户与权限的关联关系。
表结构SQL
-- 用户表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 权限表
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` int NOT NULL AUTO_INCREMENT,
`permission` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ACL表
DROP TABLE IF EXISTS `acl`;
CREATE TABLE `acl` (
`id` int NOT NULL,
`user_id` int DEFAULT NULL,
`permission_id` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `permission_id` (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
初始化数据
-- 插入用户数据
INSERT INTO `user` (id, username, password) VALUES (1, 'jack', '1');
INSERT INTO `user` (id, username, password) VALUES (2, 'rose', '1');
-- 插入权限数据
INSERT INTO `permission` (id, permission) VALUES (1, 'device:list');
INSERT INTO `permission` (id, permission) VALUES (2, 'device:add');
-- 插入ACL数据
INSERT INTO `acl` (id, user_id, permission_id) VALUES (1, 1, 1);
INSERT INTO `acl` (id, user_id, permission_id) VALUES (2, 2, 2);
1.2 ACL权限设计的代码实现
1.2.1 Mapper层
使用MyBatis-Plus实现基本的CRUD操作。
public interface UserMapper extends BaseMapper<User> {
}
public interface PermissionMapper extends BaseMapper<Permission> {
}
public interface AclMapper extends BaseMapper<Acl> {
}
1.2.2 Service层
定义Service接口及其实现类。
public interface UserService extends IService<User> {
User getByUsername(String username);
}
public interface PermissionService extends IService<Permission> {
List<Permission> findByIds(Set<Integer> permIds);
}
public interface AclService extends IService<Acl> {
List<Permission> findByUserId(Integer id);
}
1.2.3 Service实现类
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User getByUsername(String username) {
return baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username));
}
}
@Service
@Slf4j
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService {
@Override
public List<Permission> findByIds(Set<Integer> permIds) {
return baseMapper.selectBatchIds(permIds);
}
}
@Service
@Slf4j
public class AclServiceImpl extends ServiceImpl<AclMapper, Acl> implements AclService {
@Resource
private PermissionService permissionService;
@Override
public List<Permission> findByUserId(Integer id) {
List<Acl> acls = baseMapper.selectList(new LambdaQueryWrapper<Acl>().eq(Acl::getUserId, id));
Set<Integer> permIds = acls.stream().map(Acl::getPermissionId).collect(Collectors.toSet());
return permissionService.findByIds(permIds);
}
}
1.2.4 登录接口
通过登录接口获取用户的权限列表。
@GetMapping("/acl/login")
public String loginAcl(String username, String password) {
User user = userService.getByUsername(username);
if (Objects.nonNull(user)) {
if (!user.getPassword().equals(password)) {
return "密码错误!";
}
// 根据用户获取ACL权限列表
List<Permission> permissionList = aclService.findByUserId(user.getId());
return permissionList.toString();
} else {
return "用户名不存在";
}
}
1.2.5 测试
访问以下URL,测试ACL权限控制:
http://localhost:8080/acl/login?username=jack&password=1
返回结果:
[Permission(id=1, permission=device:list)]
第二章:RBAC权限控制
2.1 表结构设计
RBAC的核心是用户通过角色与权限关联,因此我们需要设计四张表:
- 用户表(User):存储用户信息。
- 角色表(Role):存储角色信息。
- 权限表(Permission):存储权限信息。
- 用户角色表(UserRole):存储用户与角色的关联关系。
- 角色权限表(RolePermission):存储角色与权限的关联关系。
表结构SQL
-- 角色表
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 角色权限表
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int DEFAULT NULL,
`permission_id` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 用户角色表
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int DEFAULT NULL,
`role_id` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
初始化数据
-- 插入角色数据
INSERT INTO `role` VALUES (1, '设备管理员');
-- 插入角色权限数据
INSERT INTO `role_permission` VALUES (1, 1, 1);
-- 插入用户角色数据
INSERT INTO `user_role` VALUES (1, 1, 1);
2.2 RBAC权限设计的代码实现
2.2.1 Mapper层
public interface UserMapper extends BaseMapper<User> {
List<Permission> findByUserRole(@Param("id") Integer id);
}
2.2.2 Service层
public interface UserService extends IService<User> {
List<Permission> findByUserRole(Integer id);
}
2.2.3 Service实现类
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public List<Permission> findByUserRole(Integer id) {
return baseMapper.findByUserRole(id);
}
}
2.2.4 XML映射文件
<select id="findByUserRole" resultType="com.example.entity.Permission">
SELECT e.*
FROM user a
LEFT JOIN user_role b ON a.id = b.user_id
LEFT JOIN role c ON b.role_id = c.id
LEFT JOIN role_permission d ON c.id = d.role_id
LEFT JOIN permission e ON d.permission_id = e.id
WHERE a.id = #{id}
</select>
2.2.5 登录接口
@GetMapping("/rbac/login")
public String loginRbac(String username, String password) {
User user = userService.getByUsername(username);
if (Objects.nonNull(user)) {
if (!user.getPassword().equals(password)) {
return "密码错误!";
}
// 根据用户获取RBAC权限列表
List<Permission> permissionList = userService.findByUserRole(user.getId());
return permissionList.toString();
} else {
return "用户名不存在";
}
}
2.2.6 测试
访问以下URL,测试RBAC权限控制:
http://localhost:8080/rbac/login?username=jack&password=1
返回结果:
[Permission(id=1, permission=device:list)]
总结
ACL适用于简单的权限控制场景,而RBAC更适合复杂的权限管理需求。在实际项目中,可以根据业务需求选择合适的权限控制模型,或者将两者结合使用,以实现更灵活的权限管理。