news 2026/4/23 18:37:42

SpringBoot3实战:数据库鉴权全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot3实战:数据库鉴权全攻略

前言

作为一名互联网软件开发人员,你是否曾在项目中遇到过这样的困境:Spring Security 默认的内存认证方式在实际生产环境中根本不够用,想要接入自己的用户数据库却不知从何下手?别担心,今天这篇文章将带你一步步攻克这个难题,用最接地气的方式讲解如何在 Spring Boot3 中实现基于自定义数据库的安全鉴权体系。

为什么需要自定义数据库鉴权?

在开发初期,很多人会图方便直接使用 Spring Security 提供的
InMemoryUserDetailsManager,通过硬编码的方式存储用户名和密码。但这种方式存在三个致命问题:

首先是数据持久性问题,应用重启后所有用户信息都会丢失,这在生产环境中是绝对不能接受的。其次是扩展性局限,当用户数量超过 10 个时,硬编码方式会让代码变得臃肿不堪。最后是权限管理缺失,真实业务中往往需要基于角色的细粒度权限控制,内存模式很难满足这种需求。

根据 Stack Overflow 2024 年的开发者调查显示,83% 的企业级应用都会选择数据库存储用户信息,其中 MySQL 以 67% 的占比成为最受欢迎的选择。这也是我们今天选择 MySQL 作为示例数据库的原因。

前期准备:搭建基础环境

2.1 数据库设计

首先我们需要设计用户表和角色表,这里采用最经典的多对多关系模型:

-- 用户表 CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '加密后的密码', `enabled` tinyint NOT NULL DEFAULT '1' COMMENT '是否启用(1=启用,0=禁用)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) COMMENT '用户名唯一' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 角色表 CREATE TABLE `sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', `role_name` varchar(50) NOT NULL COMMENT '角色名称', `role_code` varchar(50) NOT NULL COMMENT '角色编码', PRIMARY KEY (`id`), UNIQUE KEY `uk_role_code` (`role_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统角色表'; -- 用户角色关联表 CREATE TABLE `sys_user_role` ( `user_id` bigint NOT NULL COMMENT '用户ID', `role_id` bigint NOT NULL COMMENT '角色ID', PRIMARY KEY (`user_id`,`role_id`), KEY `fk_role_id` (`role_id`), CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`), CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

注意这里我们给用户名创建了唯一索引,这是为了避免重复注册的问题。密码字段预留了 100 位长度,因为 BCrypt 加密后的字符串通常在 60 位左右。

2.2 项目依赖配置

在pom.xml中添加必要的依赖:

<!-- Spring Boot Starter Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

为什么选择 MyBatis Plus?因为它提供的 CRUD 接口可以帮我们减少 70% 的重复代码,特别是BaseMapper中的方法完全能满足用户查询需求,这也是很多企业开发的首选方案。

2.3 数据源配置

在application.yml中配置数据库连接信息:

spring: datasource: url: jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.securitydemo.entity configuration: map-underscore-to-camel-case: true # 开启驼峰命名转换

这里有个细节需要注意:MySQL 8.0 以上版本必须指定serverTimezone,否则会出现时区错误。推荐使用Asia/Shanghai而不是UTC+8,因为在某些服务器环境下后者可能不被识别。

核心实现:自定义用户认证体系

3.1 实体类设计

创建与数据库表对应的实体类:

@Data @TableName("sys_user") public class SysUser { private Long id; private String username; private String password; private Boolean enabled; private LocalDateTime createTime; } @Data @TableName("sys_role") public class SysRole { private Long id; private String roleName; private String roleCode; }

使用 Lombok 的@Data注解可以省略 getter、setter 方法,让代码更简洁。@TableName注解指定对应的数据库表名,这是 MyBatis Plus 的规范。

3.2 Mapper 层实现

创建用户和角色的 Mapper 接口:

public interface SysUserMapper extends BaseMapper<SysUser> { /** * 根据用户名查询用户角色 */ List<SysRole> selectRolesByUsername(String username); }

在resources/mapper目录下创建SysUserMapper.xml:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.securitydemo.mapper.SysUserMapper"> <select id="selectRolesByUsername" resultType="com.example.securitydemo.entity.SysRole"> SELECT r.id, r.role_name, r.role_code FROM sys_user u LEFT JOIN sys_user_role ur ON u.id = ur.user_id LEFT JOIN sys_role r ON ur.role_id = r.id WHERE u.username = #{username} </select> </mapper>

这个查询非常关键,它通过用户 ID 关联角色表,一次性获取用户拥有的所有角色信息,这是实现基于角色的访问控制(RBAC)的基础。

3.3 实现 UserDetailsService

这是整个自定义认证中最核心的部分,我们需要实现 Spring Security 提供的UserDetailsService接口:

@Service @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { private final SysUserMapper sysUserMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1.查询用户信息 LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getUsername, username); SysUser user = sysUserMapper.selectOne(queryWrapper); if (user == null) { throw new UsernameNotFoundException("用户名不存在"); } // 2.查询用户角色 List<SysRole> roles = sysUserMapper.selectRolesByUsername(username); List<String> roleCodes = roles.stream() .map(SysRole::getRoleCode) .collect(Collectors.toList()); // 3.转换为UserDetails对象 return User.withUsername(user.getUsername()) .password(user.getPassword()) .roles(roleCodes.toArray(new String[0])) .disabled(!user.getEnabled()) .build(); } }

这段代码的执行流程是:当用户登录时,Spring Security 会调用loadUserByUsername方法,我们首先从数据库查询用户信息,如果用户不存在就抛出异常;然后查询该用户的角色列表,将角色编码转换为字符串数组;最后构建User对象返回,这个对象包含了用户名、密码、角色和启用状态等关键信息。

3.4 配置 SecurityConfig

创建安全配置类,这是整合 Spring Security 的关键:

@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { private final CustomUserDetailsService userDetailsService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/login").permitAll() // 登录接口允许匿名访问 .requestMatchers("/admin/**").hasRole("ADMIN") // admin路径需要ADMIN角色 .requestMatchers("/user/**").hasAnyRole("ADMIN", "USER") // user路径需要ADMIN或USER角色 .anyRequest().authenticated() // 其他请求需要认证 ) .formLogin(form -> form .defaultSuccessUrl("/index", true) // 登录成功后跳转的页面 ) .logout(logout -> logout .logoutSuccessUrl("/login?logout") // 退出登录后跳转的页面 ); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { // 使用BCrypt加密密码 return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }

这里我们做了几件重要的事情:

  1. 配置了 URL 访问权限:登录接口允许匿名访问,/admin/**路径需要 ADMIN 角色,/user/**路径需要 ADMIN 或 USER 角色,其他路径都需要认证后才能访问。
  2. 配置了表单登录和退出登录的跳转页面,这是 Web 应用最常用的登录方式。
  3. 定义了PasswordEncoder为BCryptPasswordEncoder,这是 Spring 官方推荐的密码加密方式,它会自动生成随机盐值,安全性非常高。
  4. 暴露了AuthenticationManager,方便我们在后续的控制器中处理登录逻辑。

功能测试:验证鉴权效果

4.1 准备测试数据

首先插入测试用户和角色:

-- 插入角色 INSERT INTO `sys_role` VALUES (1, '管理员', 'ADMIN'); INSERT INTO `sys_role` VALUES (2, '普通用户', 'USER'); -- 插入用户(密码是123456加密后的结果) INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$Gd7QJ8dL9eXl8V6wLz5i5OQJQZJQZJQZJQZJQZJQZJQZJQZJQZ', 1, NOW()); INSERT INTO `sys_user` VALUES (2, 'user', '$2a$10$Gd7QJ8dL9eXl8V6wLz5i5OQJQZJQZJQZJQZJQZJQZJQZJQZJQZ', 1, NOW()); -- 关联用户角色 INSERT INTO `sys_user_role` VALUES (1, 1); -- admin拥有ADMIN角色 INSERT INTO `sys_user_role` VALUES (2, 2); -- user拥有USER角色

注意这里的密码是用BCryptPasswordEncoder加密后的结果,原始密码都是 123456。如果你需要生成新的密码,可以写一个简单的测试方法:

public class PasswordTest { public static void main(String[] args) { PasswordEncoder encoder = new BCryptPasswordEncoder(); String password = encoder.encode("123456"); System.out.println(password); } }

4.2 创建测试接口

@RestController public class TestController { @GetMapping("/index") public String index(Authentication authentication) { return "欢迎" + authentication.getName() + "登录系统"; } @GetMapping("/admin/hello") public String adminHello() { return "管理员专属接口"; } @GetMapping("/user/hello") public String userHello() { return "用户专属接口"; } }

这些接口用于验证不同角色的访问权限:

  • /index:任何登录用户都可以访问
  • /admin/hello:只有 ADMIN 角色可以访问
  • /user/hello:ADMIN 和 USER 角色都可以访问

4.3 测试场景验证

  1. 未登录访问:直接访问/index会被重定向到登录页面,这符合我们的配置。
  2. user 用户登录
  • 可以访问/index和/user/hello
  • 访问/admin/hello会出现 403 错误,提示权限不足
  1. admin 用户登录
  • 可以访问所有接口,包括/admin/hello
  • 这说明角色权限控制生效了
  1. 密码错误测试:输入错误密码会提示 "Bad credentials",这是 Spring Security 的默认提示。
  2. 用户不存在测试:输入不存在的用户名会提示 "用户名不存在",这是我们在CustomUserDetailsService中自定义的异常信息。

进阶优化:提升安全性和可维护性

5.1 密码加密存储最佳实践

虽然我们已经使用了 BCrypt 加密,但在实际开发中还有几点需要注意:

  • 注册时加密:用户注册时必须对密码进行加密处理,绝对不能明文存储
  • 密码策略:建议设置密码复杂度要求,比如长度不少于 8 位,包含大小写字母、数字和特殊符号
  • 定期更换:可以在系统中添加密码定期更换提醒功能

5.2 异常处理优化

默认的异常信息不够友好,我们可以自定义认证失败处理器:

@Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { response.setContentType("application/json;charset=utf-8"); Map<String, Object> result = new HashMap<>(); result.put("code", 401); if (exception instanceof UsernameNotFoundException) { result.put("msg", "用户名不存在"); } else if (exception instanceof BadCredentialsException) { result.put("msg", "密码错误"); } else { result.put("msg", "登录失败"); } ObjectMapper mapper = new ObjectMapper(); response.getWriter().write(mapper.writeValueAsString(result)); } }

然后在SecurityConfig中配置:

.formLogin(form -> form .defaultSuccessUrl("/index", true) .failureHandler(customAuthenticationFailureHandler) // 添加失败处理器 )

这样前端就能得到结构化的错误信息,方便进行友好提示。

5.3 权限细化控制

如果需要更细粒度的权限控制,可以将角色(Role)和权限(Permission)分离,实现基于权限的访问控制(PBAC)。基本思路是:

  1. 增加权限表和角色权限关联表
  2. 在UserDetails中添加权限信息
  3. 在配置中使用hasAuthority替代hasRole

这种方式适合权限复杂的大型系统,比如电商平台的后台管理系统。

总结

通过本文的学习,我们已经掌握了在 Spring Boot3 中使用自定义数据库实现 Spring Security 鉴权的完整流程,包括:

  1. 数据库设计和环境搭建
  2. 自定义UserDetailsService实现用户信息查询
  3. 配置SecurityConfig实现权限控制
  4. 功能测试和进阶优化

这套方案已经在很多实际项目中得到验证,完全可以满足中小型系统的安全需求。对于大型系统,你还可以在此基础上扩展:

  • 集成 JWT 实现无状态认证
  • 添加验证码、记住我等功能
  • 对接 OAuth2.0 实现第三方登录
  • 集成 Spring Security OAuth2 实现分布式认证

最后留一个思考题:如果需要实现用户的动态权限调整(不需要重启应用),你会怎么做?欢迎在评论区留下你的解决方案,我们一起交流探讨。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 9:52:17

基于Vue和Spring Boot的乡村文旅平台设计与实现开题报告

长春电子科技学院 毕业设计&#xff08;论文&#xff09;开题报告 学院 专业 学 号 学生姓名 指导教师 填 写 说 明 一、学生应认真阅读《毕业设计&#xff08;论文&#xff09;题目申报表》&#xff0c;明确了解题目的具体要…

作者头像 李华
网站建设 2026/4/23 9:52:33

中国城市形态指标(1992-2024)

D244 中国城市形态指标(1992-2024) 数据简介 今天我们分享的是中国城市形态指标数据集&#xff0c;包含road、max、center三个指标&#xff0c;该指标是参考顶刊世界经济的处理方法&#xff0c;通过夜间灯光数据(见前文)计算而来&#xff0c;整理成面板数据&#xff0c;方便大…

作者头像 李华
网站建设 2026/4/23 11:26:33

Wan2.2-T2V-5B支持生成视频自动匹配背景音乐

Wan2.2-T2V-5B&#xff1a;让AI视频生成真正“秒出片”&#xff0c;还能自动配乐&#xff1f; 你有没有试过在抖音或小红书上花半小时剪一条15秒的短视频&#xff1f;找素材、调滤镜、选BGM……最后发现&#xff0c;创意还没开始&#xff0c;精力已经耗尽了。 而现在&#xf…

作者头像 李华
网站建设 2026/4/23 6:10:42

数据库系统原理经典教材:开启数据世界大门的金钥匙

数据库系统原理经典教材&#xff1a;开启数据世界大门的金钥匙 【免费下载链接】数据库系统原理王能斌PDF版本介绍 《数据库系统原理》是王能斌编著的经典教材&#xff0c;全面系统地介绍了数据库系统的基本原理、技术与应用。本书内容涵盖数据库基本概念、关系模型、SQL语言、…

作者头像 李华
网站建设 2026/4/23 12:45:43

【量子算法性能优化指南】:VSCode中高效调试与分析的5大核心技巧

第一章&#xff1a;量子算法的 VSCode 性能分析在开发和调试量子算法时&#xff0c;性能分析是优化执行效率的关键环节。Visual Studio Code&#xff08;VSCode&#xff09;凭借其强大的扩展生态系统&#xff0c;成为量子计算开发者常用的集成开发环境。通过结合 Q#、Python 与…

作者头像 李华
网站建设 2026/4/23 14:59:04

Wan2.2-T2V-A14B模型对肢体语言与情绪表达的捕捉精度

Wan2.2-T2V-A14B&#xff1a;当AI开始“读懂人心”的那一刻 &#x1f92f; 你有没有想过&#xff0c;有一天AI不仅能听懂你说什么&#xff0c;还能看穿你的情绪&#xff1f; 不是靠读心术&#xff0c;而是通过一个眼神、一次抬手、一缕颤抖的嘴角——把文字里藏着的“情绪暗流”…

作者头像 李华