目录
一、引入依赖及原理简述
二、多用户维护配置 <— 你在这里 ( •̀ ω •́ )y
三、基础自定义配置
四、前后端分离及登录结果管理
五、角色权限管理基础


从此处开始,为新的原创内容,相关数据结构代码换了一套新的,与之前的代码关系不大了。
建议新建一个项目,将配置文件复制过来,然后按照步骤走。

六、RBAC 结构实现
七、自定义响应式登录与 JWT 配置
八、集成 Redis

Spring Security(二)多用户维护配置

博主前言:本以为这个就是代替传统 jwt 的插件,没想到复杂程度如此之高。Spring Security 本身是个高度自定义化的组件,必须花时间重点学习一下。以下为个人配置学习的流程,从零到权限管理、redis嵌入等步骤。
本文基于尚硅谷的 Spring Security 教程学习,文章与原教程有不小出入,仅供参考。
B站视频链接:尚硅谷Java项目SpringSecurity+OAuth2权限管理实战教程

​ 在实际开发需求中,我们不可能只有一个用户需要维护,好在 Spring Security 有一套完整的流程支持这一操作。


一、基于内存的用户认证

  1. 配置类

​ 先来见见 Spring Security 的Config类:

1
2
3
4
@Configuration
@EnableWebSecurity // 开启 Spring Security 自定义配置
public class SecurityConfig {
}

实际上,Spring boot 项目不需要添加@EnableWebSecurity,因为其autoconfig包会默认启用依赖的相关配置。

  1. 基于内存的用户信息管理器

​ 在Config类内创建基于内存的用户信息管理器Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public UserDetailsService userDetailsService() {
// 基于内存的用户信息管理器
var manager = new InMemoryUserDetailsManager();

// 在内存中创建一个用户认证信息(UserDetails)
// 该方法可以重复添加用户(用户名不能重复)
manager.createUser(User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build());

return manager;
}

UserDetailsService的作用:储存并维护用户认证信息的服务类,其对应的用户认证信息类为UserDetails

UserDetails的作用:包含用户名、密码、权限等内容。

UserDetailsServiceUserDetails均为抽象接口,前者为不同的维护方式,后者则根据实际情况具体实现。

上述代码中的User即为 Spring Security 默认提供的UserDetails实现。

PasswordEncoder为密码的加密编码器,该对象使用默认编码(已弃用),加密编码器可以全局指定 Spring Security 自带的更好的编码器(强哈希BCryptPasswordEncoder()),亦可以自定义。

​ 此时,默认配置失效,因为我们已经重写了UserDetailsService,默认配置不再受理。


二、基于数据库的数据源的用户认证

接下来的 SQL 部分基于 Mysql 和 Mybatis-Plus,使用了 Lombok 辅助对象编写,相关依赖及功能这里不再赘述。

Mybatis-Plus 是 Mybatis 的扩展,不对原生 Mybatis 有影响。

  1. 创建数据表

表创建好之后,记得随便放进去一些测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 创建用户表
CREATE TABLE `user` (
`id` int NOT NULL,
`username` varchar(50) COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
`password` varchar(500) COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
`enable` tinyint NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `user_username_unidex` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_ci;

-- 插入测试数据(密码是 "password")
INSERT INTO `user` ( `id`, `username`, `password`, `enabled`) VALUES
('0', 'admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('1', 'Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('2', 'Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
  1. 准备对应数据类、持久层类、服务层类和控制层类
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
// 用户数据类
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Integer id;
private String username;
private String password;
private Boolean enable;
}

// 用户持久层类
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

// 用户服务层接口
public interface IUserService extends IService<User> {
}

// 用户服务层类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

// 用户控制层类
@RestController
@RequestMapping
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;

@GetMapping("/list")
public List<User> getList() {
return userService.list();
}
}
  1. 自定义UserDetailsService

​ 基于内存的用户认证中,我们已经了解到UserDetailsService是维护用户认证的服务类。在基于内存的方式,有 Spring Security 提供的封装类,我们可以通过Bean注入来装配。但数据表的结构复杂多变,对于基于数据库的数据认证,我们需要自己编写一个UserDetailsService类来适配我们的数据表。

别忘了注释掉SecurityConfig中基于内存的用户信息管理器Bean

​ 对于我自己来说,我的做法更加大胆:将用户服务层接口继承UserDetailsService。原本用户逻辑的维护就是用户服务类的任务之一,将UserDetailsService重新整合回用户服务在设计上没有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 改造后的用户服务层接口
public interface IUserService extends IService<User>, UserDetailsService {
}

// 改造后的用户服务层类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var userLQW = new LambdaQueryWrapper<User>()
.eq(User::getUsername, username);
var user = this.getOne(userLQW);
if (user == null)
throw new UsernameNotFoundException("用户不存在:" + username);
return user;
}
}

​ 同时,我们需要注意一件事:UserDetailsService管理的是UserDetails,而非我们自定义的User对象。所以,我们要将User继承UserDetails接口,从而交给UserDetailsService管理(经过上面的操作,实际管理的是用户服务层接口)。

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
// 改造后的 User
@Data
public class User implements UserDetails {
@TableId(type = IdType.ASSIGN_ID)
private Integer id;
private String username;
private String password;
private Boolean enable;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 没有权限设定,返回空列表
return List.of();
}

@Override
public boolean isEnabled() {
return enable;
}

@Override
public boolean isAccountNonExpired() {
return UserDetails.super.isAccountNonExpired();
}

@Override
public boolean isAccountNonLocked() {
return UserDetails.super.isAccountNonLocked();
}

@Override
public boolean isCredentialsNonExpired() {
return UserDetails.super.isCredentialsNonExpired();
}
}

​ 至此,基于数据库的数据源的用户认证配置完成。