news 2026/4/3 4:49:40

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/3/31 22:41:49

OpenSCA-cli终极指南:5分钟掌握软件依赖安全检测

在当今开源软件盛行的时代&#xff0c;软件成分分析已成为保障应用安全的关键环节。OpenSCA-cli作为一款开源的软件成分分析工具&#xff0c;能够快速扫描项目中的第三方组件依赖、识别安全问题及许可证风险&#xff0c;为开发者和企业提供简单高效的解决方案。 【免费下载链接…

作者头像 李华
网站建设 2026/3/26 13:31:31

如何通过容器系统打造企业级日历界面?

如何通过容器系统打造企业级日历界面&#xff1f; 【免费下载链接】caesium-image-compressor Caesium is an image compression software that helps you store, send and share digital pictures, supporting JPG, PNG and WebP formats. You can quickly reduce the file si…

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

跨屏投送新体验:Macast媒体共享全攻略

跨屏投送新体验&#xff1a;Macast媒体共享全攻略 【免费下载链接】Macast Macast - 一个跨平台的菜单栏/状态栏应用&#xff0c;允许用户通过 DLNA 协议接收和发送手机中的视频、图片和音乐&#xff0c;适合需要进行多媒体投屏功能的开发者。 项目地址: https://gitcode.com…

作者头像 李华
网站建设 2026/4/2 19:59:39

MetaMCP:终极统一MCP管理工具,轻松实现多服务器集中管理

MetaMCP&#xff1a;终极统一MCP管理工具&#xff0c;轻松实现多服务器集中管理 【免费下载链接】metatool-app 项目地址: https://gitcode.com/gh_mirrors/me/metatool-app 在当今AI开发领域&#xff0c;MCP&#xff08;Model Context Protocol&#xff09;服务器已经…

作者头像 李华
网站建设 2026/3/15 20:11:10

8、网络连接调制解调器配置全解析

网络连接调制解调器配置全解析 1. 调制解调器概述 调制解调器是网络市场中大部分连接的媒介。想象一下,当地的互联网服务提供商(ISP)可能配备了许多调制解调器,每个调制解调器都被成百甚至上千人用作连接互联网的桥梁。 2. Minicom 实用工具 Minicom 是一个基于文本的基…

作者头像 李华
网站建设 2026/3/26 11:51:48

华为访问控制列表的配置

任务一&#xff1a;基本访问控制列表的配置一、基础配置RA&#xff1a;<Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]undo info-center ena Info: Information center is disabled. [Huawei]sysn RA [RA]int g0/0/0 [RA-GigabitEthernet0/0/0]i…

作者头像 李华