目录 一、引入依赖及原理简述 二、多用户维护配置 三、基础自定义配置 四、前后端分离及登录结果管理 五、角色权限管理基础
从此处开始,为新的原创内容,相关数据结构代码换了一套新的,与之前的代码关系不大了。 建议新建一个项目,将配置文件复制过来,然后按照步骤走。
六、RBAC 结构实现 <— 你在这里 ( •̀ ω •́ )y 七、自定义响应式登录与 JWT 配置 八、集成 Redis
Spring Security(六)RBAC 结构实现
博主前言:本以为这个就是代替传统 jwt 的插件,没想到复杂程度如此之高。Spring Security 本身是个高度自定义化的组件,必须花时间重点学习一下。以下为个人配置学习的流程,从零到权限管理、redis嵌入等步骤。 本文基于尚硅谷的 Spring Security 教程学习,文章与原教程有不小出入,仅供参考。 B站视频链接:尚硅谷Java项目SpringSecurity+OAuth2权限管理实战教程
RBAC(用户 - 角色 - 权限 - 资源)是目前广泛应用的权限结构,该结构能够动态管理权限,一般为三个对象模型和两个链接模型:
如图,用户可以有多个角色身份,角色可以被分配多种权限。
接下来我们要实现这个结构。
[!WARNING]
特殊注意,此角色非彼角色:
虽然我们设计了【角色】这个模型,但是在代码层面上,【角色】与代码的耦合度非常高——管理端和用户端的授权注解都是硬编码。
实际上,【角色】表为权限的打包集合,用于为用户分配权限及划分类别。
所以,若为单端系统,代码层面无需配置角色;若为双端乃至多端系统,代码层面每一端配置一个角色。
一、数据表实现 一共五个表,三个主表两个链接表。
用户表user
列名
数据类型
描述
id
int
用户ID
username
varchar
用户名
password
varchar
密码
status
tinyint
状态(启用/禁用)
…
…
…
角色表role
列名
数据类型
描述
id
int
角色ID
name
varchar
角色名称
type
varchar
所属角色组
用户-角色链接表lk_user_role
列名
数据类型
描述
id
int
用户角色关联ID
user_id
int
用户ID
role_id
int
角色ID
权限表permission
列名
数据类型
描述
id
int
权限ID
name
varchar
权限名称
value
varchar
权限值
角色-权限链接表lk_role_permission
列名
数据类型
描述
id
int
用户角色关联ID
role_id
int
角色ID
permission_id
int
权限ID
建表语句和测试数据:
[!NOTE]
构建对应的Mybatis-Plus
结构这里不再赘述。
密码均为 password
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY , username VARCHAR (255 ) NOT NULL , password VARCHAR (255 ) NOT NULL , status TINYINT(1 ) DEFAULT 1 , UNIQUE (username) ); CREATE TABLE role ( id INT AUTO_INCREMENT PRIMARY KEY , name VARCHAR (255 ) NOT NULL , type VARCHAR (16 ) NOT NULL ); CREATE TABLE lk_user_role ( id INT AUTO_INCREMENT PRIMARY KEY , user_id INT NOT NULL , role_id INT NOT NULL , FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE, FOREIGN KEY (role_id) REFERENCES role(id) ON DELETE CASCADE ); CREATE TABLE permission ( id INT AUTO_INCREMENT PRIMARY KEY , name VARCHAR (255 ) NOT NULL , value VARCHAR (32 ) NOT NULL ); CREATE TABLE lk_role_permission ( id INT AUTO_INCREMENT PRIMARY KEY , role_id INT NOT NULL , permission_id INT NOT NULL , FOREIGN KEY (role_id) REFERENCES role(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permission(id) ON DELETE CASCADE ); INSERT INTO `lk_role_permission` VALUES (1 , 1 , 1 );INSERT INTO `lk_role_permission` VALUES (2 , 1 , 2 );INSERT INTO `lk_role_permission` VALUES (3 , 2 , 2 );INSERT INTO `lk_user_role` VALUES (4 , 1 , 1 );INSERT INTO `lk_user_role` VALUES (5 , 2 , 2 );INSERT INTO `permission` VALUES (1 , '查询用户列表' , 'user_list' );INSERT INTO `permission` VALUES (2 , '查询自己' , 'user_myself' );INSERT INTO `role` VALUES (1 , '管理员' , 'admin' );INSERT INTO `role` VALUES (2 , '普通用户' , 'user' );INSERT INTO `user ` VALUES (1 , 'admin' , '$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW' , 1 );INSERT INTO `user ` VALUES (2 , 'user' , '$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW' , 1 );
二、重写对应逻辑 重写User
结构,适配UserDetails
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Data public class User implements UserDetails { @TableId(type = IdType.AUTO) private Integer id; private String username; private String password; private Integer status; @TableField(exist = false) private List<GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority > getAuthorities() { return authorities; } @Override public String getUsername () { return username; } @Override public String getPassword () { return password; } @Override public boolean isEnabled () { return status == 1 ; } @Override public boolean isAccountNonExpired () { return UserDetails.super .isAccountNonExpired(); } @Override public boolean isAccountNonLocked () { return UserDetails.super .isAccountNonLocked(); } @Override public boolean isCredentialsNonExpired () { return UserDetails.super .isCredentialsNonExpired(); } }
重写UserDetailService
中的用户维护校验方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Service @RequiredArgsConstructor public class UserServiceImpl extends ServiceImpl <UserMapper, User> implements UserService { private final PasswordEncoder passwordEncoder; private final RoleService roleService; private final PermissionService permissionService; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { var user = getByUsername(username); if (user == null ) throw new UsernameNotFoundException ("用户不存在:" + username); var authorityList = new ArrayList <GrantedAuthority>(); List<Role> roleList = roleService.getByUserId(user.getId()); roleList.forEach(role -> { authorityList.add(() -> "ROLE_" + role.getType()); List<Permission> permissionList = permissionService.getByRoleId(role.getId()); permissionList.forEach(permission -> authorityList.add(permission::getValue)); }); user.setAuthorities(authorityList); return user; } private User getByUsername (String username) { return this .getOne(new LambdaQueryWrapper <User>() .eq(User::getUsername, username)); } }
对应的链接键查找方法:
[!NOTE]
读者可以参考这篇文章来更好的配置链接键:代码层多对多结构的通用处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Service @RequiredArgsConstructor public class RoleServiceImpl extends ServiceImpl <RoleMapper, Role> implements RoleService { private final LkUserRoleService lkUserRoleService; @Override public List<Role> getByUserId (Integer id) { var lkLQW = new LambdaQueryWrapper <LkUserRole>() .eq(LkUserRole::getUserId, id); var lkList = lkUserRoleService.list(lkLQW); var resList = new ArrayList <Role>(); lkList.forEach(lk -> resList.add(this .getById(lk.getRoleId()))); return resList; } } @Service @RequiredArgsConstructor public class PermissionServiceImpl extends ServiceImpl <PermissionMapper, Permission> implements PermissionService { private final LkRolePermissionService lkPermissionService; @Override public List<Permission> getByRoleId (Integer id) { var lkLQW = new LambdaQueryWrapper <LkRolePermission>() .eq(LkRolePermission::getRoleId, id); var lkList = lkPermissionService.list(lkLQW); var resList = new ArrayList <Permission>(); lkList.forEach(lk -> resList.add(this .getById(lk.getPermissionId()))); return resList; } }
三、测试多端权限 配置Config
类:
1 2 3 4 5 6 7 http.authorizeHttpRequests(authorize -> authorize .requestMatchers("/admin/**" ).hasRole("admin" ) .requestMatchers("/user/**" ).hasRole("user" ) .anyRequest().authenticated())
编写用户列表接口:
1 2 3 4 5 6 7 8 9 10 11 @RestController @RequestMapping("/user") @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping("/list") public Result<List<User>> getList () { return Result.success(userService.list()); } }
登录并访问,发现仅user
可以获取列表,admin
无权限。
四、测试接口权限 配置Config类:
添加@EnableMethodSecurity注解
注释【配置不同终端访问权限】
修改接口:
1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RequestMapping("/user") @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping("/list") @PreAuthorize("hasAuthority('user_list')") public Result<List<User>> getList () { return Result.success(userService.list()); } }
再次登录并访问,这一次仅admin
可以获取列表,user
无权限。