一、前言
在 Spring Boot 项目中,Spring Security 默认使用内存用户进行认证,这显然无法满足实际业务需求。本文将详细讲解如何整合 MyBatis-Plus 从数据库加载用户信息,实现自定义用户认证流程,让你彻底理解 Spring Security 认证的底层逻辑。
二、核心认证流程
整个登录认证流程可以分为 6 个核心阶段,清晰展示了「用户请求 → 框架拦截 → 数据库查询 → 密码验证」的完整链路:
1. 用户发起登录请求
- 用户在前端输入用户名和密码,点击登录,前端将信息封装为 HTTP 请求(如
POST /login)发送到后端。 - 后端控制器(如
UserController)接收该请求。
2. Spring Security 拦截请求
- Spring Security 的核心过滤器
UsernamePasswordAuthenticationFilter自动拦截登录请求。 - 从请求中提取用户名和明文密码,封装为
UsernamePasswordAuthenticationToken(待认证凭证)。
3. 加载数据库用户信息
- Spring Security 调用自定义实现的
UserDetailsService接口(即UserDetailsServiceImpl),执行loadUserByUsername方法。 - 该方法通过 MyBatis-Plus 构建查询条件,从数据库中查询对应用户名的用户记录。
4. 适配为安全框架识别的用户对象
- 数据库查询返回的
User是业务实体类,Spring Security 无法直接识别。 - 通过
UserDetailsImpl实现类,将User包装为 Spring Security 标准的UserDetails接口对象,完成「业务对象 → 安全对象」的适配。
5. 密码比对与认证判定
- Spring Security 从
UserDetailsImpl中获取数据库存储的加密密码。 - 使用配置的密码加密器(如 BCrypt)对用户输入的明文密码加密,并与数据库密码比对。
- 密码一致则认证成功,否则认证失败。
6. 认证结果处理
- 成功 :将用户信息和权限封装为
Authentication对象,存入SecurityContext(安全上下文),后续请求可直接获取用户信息。 - 失败:抛出对应异常(如用户名不存在、密码错误),返回 401/403 错误码。
三、核心代码详解
1. UserDetailsServiceImpl:加载用户的核心实现
java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper; // MyBatis-Plus 数据访问层
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 构建查询条件:WHERE username = ?
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
// 2. 执行数据库查询,获取业务用户对象
User user = userMapper.selectOne(queryWrapper);
// 3. 用户不存在时抛出规范异常
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 4. 将业务用户适配为 Spring Security 识别的 UserDetails 对象
return new UserDetailsImpl(user);
}
}
代码职责:
- 实现
UserDetailsService接口,是 Spring Security 加载用户的唯一入口。 - 借助 MyBatis-Plus 快速构建查询,从数据库获取用户数据。
- 完成「业务用户 → 安全用户」的适配转换。
2. UserDetailsImpl:业务用户与安全框架的适配器
java
public class UserDetailsImpl implements UserDetails {
private final User user;
private final List<GrantedAuthority> authorities;
// 构造方法:接收业务 User 对象,完成权限转换
public UserDetailsImpl(User user) {
this.user = user;
// 将业务角色(如 "admin")转换为 Spring Security 识别的权限格式
this.authorities = Collections.singletonList(
new SimpleGrantedAuthority("ROLE_" + user.getRole())
);
}
// 以下方法均委托给业务 User 对象
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public String getPassword() {
return user.getPassword(); // 数据库中存储的加密密码
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
// 账号状态方法,默认返回 true(业务需要时可修改)
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
代码职责:
- 实现
UserDetails接口,是 Spring Security 识别用户的标准格式。 - 封装业务
User对象,提供用户名、加密密码、权限集合等核心信息。 - 定义账号状态(是否过期、锁定等),满足安全框架的校验需求。
四、关键类/接口职责分工
| 类/接口 | 核心职责 |
|---|---|
UserDetailsService |
Spring Security 标准接口,定义「根据用户名加载用户」的规范 |
UserDetailsServiceImpl |
自定义实现类,负责从数据库查询用户 |
UserMapper |
MyBatis-Plus 数据访问层,与数据库交互执行 CRUD |
User |
业务实体类,对应数据库用户表 |
UserDetails |
Spring Security 标准接口,定义「认证+授权所需的用户信息」 |
UserDetailsImpl |
自定义实现类,负责业务用户 → 安全用户的适配转换 |
五、总结与拓展
1. 核心本质
你编写的 UserDetailsServiceImpl + UserDetailsImpl,本质是为 Spring Security 提供了一个「数据库用户数据源」:
- 替代了默认的内存用户,让认证逻辑基于业务数据库。
- 完成了「业务数据」与「安全框架」的解耦,符合开闭原则。
2. 优化建议
- 异常规范 :务必抛出
UsernameNotFoundException,而非RuntimeException,符合 Spring Security 规范。 - 密码加密:数据库中必须存储加密后的密码(推荐 BCrypt),Spring Security 会自动比对。
- 权限细化 :可根据业务需求,将
getAuthorities()扩展为多权限/多角色模式。 - 状态控制 :通过修改
isAccountNonLocked()等方法,实现账号锁定、过期等业务逻辑。