第二章 整合 MyBatis 实现数据库认证
本章将介绍如何将 MySQL 数据库中的用户信息接入 Spring Security,告别配置文件中的内存用户。
在上一篇文章中,我们使用配置文件指定内存用户,这在开发测试环境中很方便,但实际项目里用户信息必然存储在数据库中。本篇文章将详细介绍如何整合 MyBatis,实现基于数据库的自定义认证。
一、问题切入
第一章中我们通过配置文件指定用户:
spring:
security:
user:
name: flittly
password: 123456
这种方式存在明显缺陷:
- 用户信息写在代码中,无法动态管理
- 密码以明文形式存储,安全性低
- 生产环境不应使用配置文件明文密码
- 无法扩展更多用户,无法支持注册/注销等功能
我们需要一个可扩展的方案,将数据库中的用户信息加载到 Spring Security 中。
二、解决方案:引入 MyBatis
在 pom.xml 中添加 MyBatis Starter 和 MySQL 驱动:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
三、配置数据源连接
在 application.yml 中配置数据库连接:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring_security_demo
username: root
password: 123456
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.edu.nnu.opengms.ss02.entity
四、原理阐释:认证流程
4.1 核心接口
Spring Security 的认证体系围绕以下核心接口展开:
接口
作用
UserDetailsService
根据用户名加载用户信息
UserDetails
用户信息封装对象
PasswordEncoder
密码验证器
Authentication
认证令牌
4.2 认证流程详解
整个认证流程涉及多个组件的协作:
用户提交登录表单(用户名 + 密码)
↓
UsernamePasswordAuthenticationFilter 拦截请求
↓ 提取用户名
DaoAuthenticationProvider.getUserDetails(username)
↓ 调用
UserDetailsService.loadUserByUsername(username)
↓ 调用 MyBatis Mapper
从数据库查询用户信息
↓ 返回
构建 UserDetails 对象(含密码、权限信息)
↓
PasswordEncoder.matches() 验证密码
↓ 匹配成功
Authentication 认证成功,创建认证令牌
↓
SecurityContextHolder.getContext().setAuthentication()
↓
重定向到成功页面
UserDetailsService 是 Spring Security 认证体系的核心接口,负责根据用户名加载用户信息。
4.3 密码编码器
Spring Security 7.x 要求必须配置密码编码器,支持多种编码方式:
- BCrypt:推荐,单向哈希算法,内置随机盐
- Argon2:现代密码哈希算法
- PBKDF2:基于密钥推导
- Scrypt:计算密集型算法
BCrypt 是目前最常用的选择,它具有以下特点:
- 单向哈希:无法从哈希值反向推导原始密码
- 内置盐:每次加密生成不同的哈希值,即使相同密码也不一样
- 强度可调:通过 work factor 调整计算时间
五、代码实现
5.1 Users 实体类
对应数据库中的 users 表:
@Data
public class Users implements Serializable {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private Boolean enabled;
private Date createTime;
private Date updateTime;
}
5.2 Mapper 接口
定义根据用户名查询用户的方法:
@Mapper
public interface UsersMapper {
Users selectByLoginAct(@Param("loginAct") String loginAct);
}
5.3 Mapper XML
<mapper namespace="cn.edu.nnu.opengms.ss02.mapper.UsersMapper">
<select id="selectByLoginAct" resultMap="BaseResultMap">
SELECT * FROM users WHERE username = #{loginAct}
</select>
</mapper>
5.4 UserService 接口
定义服务接口,继承 UserDetailsService:
public interface UserService extends UserDetailsService {
// 只需实现 loadUserByUsername 方法
}
5.5 UserServiceImpl 实现
核心实现类,从数据库加载用户:
@Service
public class UserServiceImpl implements UserService {
@Resource
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = usersMapper.selectByLoginAct(username);
if (users == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 将数据库用户转换为 Spring Security 的 UserDetails 对象
UserDetails userDetails = User.builder()
.username(users.getUsername())
.password(users.getPassword())
.authorities(AuthorityUtils.NO_AUTHORITIES)
.build();
return userDetails;
}
}
这里的
User是 Spring Security 提供的构建器类,用于简化UserDetails对象的创建。
5.6 PasswordEncoder 配置
在 Spring 容器中注册密码编码器:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// BCrypt:单向哈希算法,自带随机盐,安全性高
return new BCryptPasswordEncoder();
}
}
六、数据库准备
创建数据库和用户表:
CREATE TABLE `users` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(100) NOT NULL,
`email` VARCHAR(100),
`phone` VARCHAR(20),
`enabled` TINYINT(1) DEFAULT 1,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:数据库中的密码必须是 BCrypt 加密后的密文,可以使用 BCrypt 工具类生成。
6.1 使用 IDEA 插件 Free MyBatis Tool 生成 Mapper 与 XML
在这个模块里,你也可以使用 IntelliJ IDEA 插件 Free MyBatis Tool 来快速生成基础代码,减少手写重复劳动。
常见流程如下:
- 在 IDEA 安装插件:
Free MyBatis Tool - 配置数据源并连接目标数据库(如
spring_security_demo) - 选中目标表(如
users)后执行代码生成 - 生成实体类、Mapper 接口、Mapper XML(以及可选 Service/Controller 模板)
生成后建议重点检查以下几项(这一步很重要):
namespace是否与 Mapper 接口全限定名一致- XML 中
id是否与接口方法名一致(如selectByLoginAct) mapper-locations是否与 XML 实际路径一致- 参数名与 SQL 占位符是否一致(必要时配合
@Param)
示例(生成后可按项目需要补充自定义查询):
@Mapper
public interface UsersMapper {
Users selectByLoginAct(@Param("loginAct") String loginAct);
}
<mapper namespace="cn.edu.nnu.opengms.ss02.mapper.UsersMapper">
<select id="selectByLoginAct" resultMap="BaseResultMap">
SELECT * FROM users WHERE username = #{loginAct}
</select>
</mapper>
提示:代码生成能提高效率,但不要完全依赖默认模板。安全相关字段(如
password、enabled)和登录查询 SQL 仍建议人工复核。
七、核心概念总结
概念
说明
UserDetailsService
自定义认证服务接口,loadUserByUsername() 方法由框架自动调用
UserDetails
用户信息封装对象,包含用户名、密码、权限
UserDetailsBuilder
构建器,简化 UserDetails 对象创建
PasswordEncoder
密码编码器,Spring Security 7.x 必须配置
认证流程
框架自动调用数据库查询,验证密码
八、总结
本篇文章介绍了以下核心概念:
- UserDetailsService:自定义认证服务接口
- UserDetails:用户信息封装对象
- PasswordEncoder:密码编码器,必须配置
- 认证流程:框架自动调用数据库查询,验证密码
- BCrypt:推荐使用的密码编码算法
下一篇文章我们将介绍如何自定义登录页面。
编辑者:Flittly
更新时间:2026年4月