【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证

第二章 整合 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 来快速生成基础代码,减少手写重复劳动。

常见流程如下:

  1. 在 IDEA 安装插件:Free MyBatis Tool
  2. 配置数据源并连接目标数据库(如 spring_security_demo
  3. 选中目标表(如 users)后执行代码生成
  4. 生成实体类、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>

提示:代码生成能提高效率,但不要完全依赖默认模板。安全相关字段(如 passwordenabled)和登录查询 SQL 仍建议人工复核。


七、核心概念总结

概念 说明
UserDetailsService 自定义认证服务接口,loadUserByUsername() 方法由框架自动调用
UserDetails 用户信息封装对象,包含用户名、密码、权限
UserDetailsBuilder 构建器,简化 UserDetails 对象创建
PasswordEncoder 密码编码器,Spring Security 7.x 必须配置
认证流程 框架自动调用数据库查询,验证密码

八、总结

本篇文章介绍了以下核心概念:

  1. UserDetailsService:自定义认证服务接口
  2. UserDetails:用户信息封装对象
  3. PasswordEncoder:密码编码器,必须配置
  4. 认证流程:框架自动调用数据库查询,验证密码
  5. BCrypt:推荐使用的密码编码算法

下一篇文章我们将介绍如何自定义登录页面。


编辑者 :Flittly
更新时间:2026年4月

相关推荐
逸Y 仙X8 小时前
文章二十四:Elasticsearch查询排序应用实战e
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
記億揺晃着的那天8 小时前
Claude Code 系统提示词里的安全底线:OWASP Top 10
安全·ai·ai编程·vibe coding·claude code
aXin_ya9 小时前
微服务 第十天 (Redis多级缓存)
java·redis·微服务
重明链迹实验室9 小时前
重明链迹丨每周区块链安全要闻(0427-0503)
安全·web3·区块链
逸Y 仙X9 小时前
文章二十五:ElasticSearch 分页查询
java·大数据·数据库·elasticsearch·搜索引擎·全文检索
ch.ju9 小时前
Java programming(The third edition) Chapter Two——Null return value
java·开发语言
1.14(java)9 小时前
Spring事务和事务传播机制
java·数据库·spring
AI人工智能+电脑小能手9 小时前
【大白话说Java面试题】【Java基础篇】第34题:String、StringBuffer和StringBuilder的区别是什么
java·后端·面试
晓庆的故事簿9 小时前
JAVA搭配RabbitMQ使用
java·rabbitmq·java-rabbitmq
折哥的程序人生 · 物流技术专研9 小时前
第3篇:为何要配置环境变量?
java·开发语言·后端·面试