在前面的文章中我们介绍了 《Spring Boot 3 集成 Spring Security(1)认证》和 《Spring Boot 3 集成 Spring Security(2)授权》,这篇博客将介绍怎样在 Spring Boot 3 项目中,整合 Spring Security 和 MyBatis-Plus ,轻松实现基于数据库的用户访问控制、权限管理。
准备工作
新建项目
springboot3-security-mysql-example 引入依靠
引入MyBatis-Plus依靠
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的根本上只做增强不做改变,为简化开发、进步服从而生。
留意事项
版本 3.5.9+ 开始修改为可选依靠,具体查看下文 maven bom 部门。
- <mybatisplus.version>3.5.9</mybatisplus.version>
复制代码- <!-- MyBatis-Plus https://baomidou.com-->
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-jsqlparser</artifactId>
- </dependency>
复制代码- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-bom</artifactId>
- <version>${mybatisplus.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
复制代码 创建表布局
这里我们定义三张表,来实现用户角色权限的操作

- -- 用户表
- CREATE TABLE `sys_user` (
- `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
- `username` VARCHAR ( 64 ) DEFAULT NULL COMMENT '用户名',
- `password` VARCHAR ( 64 ) DEFAULT NULL COMMENT '密码',
- `sex` CHAR ( 1 ) DEFAULT '0' COMMENT '性别 0 男 1 女 2 未知',
- `nick_name` VARCHAR ( 64 ) DEFAULT NULL COMMENT '昵称',
- `status` CHAR ( 1 ) DEFAULT '1' COMMENT '账号状态 0 禁用 1 启用',
- `valid` INT DEFAULT '1' COMMENT '有效状态 0 无效 1 有效',
- PRIMARY KEY ( `id` )
- ) ENGINE = INNODB COMMENT = '用户';
- INSERT INTO `sys_user` (`id`, `username`, `password`, `sex`, `nick_name`, `status`, `valid`) VALUES (1, 'admin', '$2a$10$xZdonloiiL6YfoLZv6mrJuvxtD238uHPIKkVDpQKBuZxzMDpTf8uK', '0', '管理员张三', '1', 1);
- INSERT INTO `sys_user` (`id`, `username`, `password`, `sex`, `nick_name`, `status`, `valid`) VALUES (2, 'user', '$2a$10$evM9SfvuN/E.ykWWOf6b3eTltPvuc6XjwW/qIhagSjlsTfi9l26Ba', '0', '用户李四', '1', 1);
- -- 角色表
- CREATE TABLE `sys_role` (
- `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
- `name` VARCHAR ( 64 ) DEFAULT NULL COMMENT '角色名',
- `code` VARCHAR ( 64 ) DEFAULT NULL COMMENT '密码',
- `status` CHAR ( 1 ) DEFAULT '1' COMMENT '状态 0 禁用 1 启用',
- `valid` INT DEFAULT '1' COMMENT '有效状态 0 无效 1 有效',
- PRIMARY KEY ( `id` )
- ) ENGINE = INNODB COMMENT = '角色';
- INSERT INTO `sys_role` (`id`, `name`, `code`, `status`, `valid`) VALUES (1, '管理员', 'ROOT', '1', 1);
- INSERT INTO `sys_role` (`id`, `name`, `code`, `status`, `valid`) VALUES (2, '普通用户', 'USER', '1', 1);
- -- 用户角色关系表
- CREATE TABLE `sys_user_role` (
- `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
- `user_id` bigint DEFAULT NULL COMMENT '用户ID',
- `role_id` bigint DEFAULT NULL COMMENT '角色ID',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = INNODB COMMENT = '用户角色关系表';
- INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`) VALUES (1, 1, 1);
- INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`) VALUES (2, 2, 2);
复制代码 默认设置账户密码123456,在数据库中使用加密后的密码,关于密码加密,可以使用下面的测试方法。
- public static void main(String[] args) {
- BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- String result = encoder.encode("123456");
- // 输出加密后的密码
- System.out.println(result);
- // 对比加密后的密码和明文密码
- System.out.println(encoder.matches("123456", result));
- }
复制代码 生成根本代码
这里我用了代码生成器插件,以进步生产服从,想具体了解,可以去官网上搭建使用。这里就不多说啦
当地代码勾选,使用 mybatis-plus 3
准备工作到这里基本上就可以了,接下来开始实现从数据库中读取用户角色权限
逻辑实现
application.yml配置
- spring:
- thymeleaf:
- # 设置Thymeleaf模板文件的前缀位置(默认是`src/main/resources/templates`)
- prefix: classpath:/templates/
- # 设置模板文件的后缀(默认是`.html`)
- suffix: .html
- # 设置模板模式(默认是HTML5,Thymeleaf 3中为`HTML`)
- mode: HTML
- # 开启模板缓存(开发时建议关闭,生产时开启)
- cache: false
- datasource:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://127.0.0.1:3306/security_data?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
- username: root
- password: root
- #mybatis
- mybatis-plus:
- mapper-locations: classpath*:/mapper/**/*.xml
- #实体扫描,多个package用逗号或者分号分隔
- typeAliasesPackage: cn.harry.*.domain
- global-config:
- #数据库相关配置
- db-config:
- #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
- id-type: AUTO
- # 逻辑删除全局属性名(驼峰和下划线都支持)
- logic-delete-field: valid
- logic-delete-value: 0 # 逻辑已删除值(默认为 1)
- logic-not-delete-value: 1 # 逻辑未删除值(默认为 0)
- banner: false
- #原生配置
- configuration:
- map-underscore-to-camel-case: true
- cache-enabled: false
- call-setters-on-nulls: true
- jdbc-type-for-null: 'null'
- # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码 SecurityConfig 配置
要使用 Spring Security 进行用户认证,我们需要配置 SecurityConfig,并实现自定义的 UserDetailsService 来与数据库中的用户信息进行集成。
- /**
- * @author harry
- * @公众号 Harry技术
- */
- @Configuration
- @EnableWebSecurity
- @EnableMethodSecurity(securedEnabled = true) // 开启方法级别的权限控制
- @RequiredArgsConstructor
- public class SecurityConfig {
-
- // 通过构造函数注入自定义UserDetailsService
- private final UserDetailsService userDetailsService;
- @Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests(auth -> auth
- // 公开访问
- .requestMatchers("/").permitAll()
- // 其他接口需认证
- .anyRequest().authenticated()
- )
- .userDetailsService(userDetailsService)
- // .exceptionHandling(exception -> exception
- // .authenticationEntryPoint(restAuthenticationEntryPoint)
- // .accessDeniedHandler(restfulAccessDeniedHandler)
- // )
- // 开启基于表单的登录
- .formLogin(Customizer.withDefaults())
- // // 开启注销功能
- // .logout(Customizer.withDefaults())
- // // 开启 HTTP Basic 认证
- // .httpBasic(Customizer.withDefaults())
- // // 开启 CSRF 防护
- // .csrf(Customizer.withDefaults())
- // // 开启跨域资源共享
- // .cors(Customizer.withDefaults())
- ;
- return http.build();
- }
- @Bean
- public PasswordEncoder passwordEncoder() {
- // 使用 BCrypt 进行密码加密
- return new BCryptPasswordEncoder();
- }
复制代码 自定义 UserDetailsService
想从数据库加载用户信息,就需要创建一个自定义的 UserDetailsService 实现类,它的重要作用:
用户认证:
UserDetailsService 负责从数据源(如数据库、LDAP等)中加载用户特定的安全信息,包括用户名、密码和权限(角色)。
Spring Security 使用 UserDetailsService 来验证用户提供的根据是否精确。
用户授权:
加载用户的权限信息,以便在认证乐成后进行授权检查。
权限信息通常包括用户的角色(如 ROLE_ADMIN, ROLE_USER 等),这些角色用于控制用户可以访问的资源和操作。
- /**
- * 系统用户认证 service
- *
- * @author harry
- * @公众号 Harry技术
- */
- @Slf4j
- @Service
- @RequiredArgsConstructor
- public class UserDetailsServiceImpl implements UserDetailsService {
- private final SysUserMapper sysUserMapper;
- private final SysUserRoleMapper sysUserRoleMapper;
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- try {
- // 获取登录用户信息
- SysUser user = sysUserMapper.selectByUsername(username);
- // 用户不存在、用户停用 等校验 TODO
- Long useId = user.getId();
- // 获取角色
- Set<String> roles = sysUserRoleMapper.listRoleKeyByUserId(useId);
- return new SysUserDetails(user, roles, username);
- } catch (Exception e) {
- log.error("loadUserByUsername error", e);
- }
- return null;
- }
- }
复制代码 我们根据数据库中的用户信息加载用户,并将角色转换为 Spring Security 能识别的格式。我们写一个SysUserDetails类来实现自定义Spring Security 用户对象。
- /**
- * 自定义 Spring Security 用户对象
- *
- * @author harry
- * @公众号 Harry技术
- */
- @Data
- @NoArgsConstructor
- public class SysUserDetails implements UserDetails {
- private String username;
- private SysUser sysUser;
- private Collection<SimpleGrantedAuthority> authorities;
- public SysUserDetails(SysUser user, Set<String> roles, String username) {
- this.sysUser = user;
- Set<SimpleGrantedAuthority> authorities;
- if (CollectionUtil.isNotEmpty(roles)) {
- // 标识角色 前面加上 ROLE_
- authorities = roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toSet());
- } else {
- authorities = Collections.emptySet();
- }
- this.authorities = authorities;
- this.username = username;
- }
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- // 返回当前用户的权限
- return authorities;
- }
- @Override
- public String getPassword() {
- return sysUser.getPassword();
- }
- @Override
- public String getUsername() {
- return this.username;
- }
- /**
- * 是否可用 ,禁用的用户不能身份验证
- *
- * @return 是否可用
- */
- @Override
- public boolean isEnabled() {
- return StatusEnums.ENABLE.getKey().equals(sysUser.getStatus());
- }
- }
复制代码 创建测试
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- <style>
- .content {
- width: 800px;
- height: 800px;
- text-align: center;
- line-height: 100px;
- font-size: 20px;
- flex: 1;
- flex-direction: column;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- </style>
- </head>
- <body>
- <div class="content">
- <!--去登陆 -->
- <a href="/login">去登陆</a>
-
- <!-- admin/info 接口 -->
- <a href="/admin/info">访问 admin/info 接口</a>
-
- <!-- 去首页 -->
- <a href="/user/info">访问 user/info 接口</a>
-
- <!--退出-->
- <a href="/logout">退出</a>
-
- </div>
- </body>
- </html>
复制代码
- 接口
改写接口admin/info,并配置 @PreAuthorize("hasRole('ROOT')")只有 ADMIN 角色才气访问
-
- /**
- * @author harry
- * @公众号 Harry技术
- * Spring Boot 3 集成 Spring Security(2) 授权: https://mp.weixin.qq.com/s/HzzcYIQLnch_7r7wdUarew
- */
- @Slf4j
- @RestController
- public class AdminController {
-
-
- @GetMapping("/admin/info")
- @PreAuthorize("hasRole('ROOT')") // 只有 ADMIN 角色才能访问
- public SysUserDetails adminInfo() {
- // 获取当前登录的用户信息
- SysUserDetails user = (SysUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- log.info("当前登录的用户信息:{}", user.toString());
- return user;
- }
- }
-
复制代码 改写接口user/info,并配置 @PreAuthorize("hasRole('USER')")只有 USER 角色才气访问
-
-
- @Slf4j
- @RestController
- public class UserController {
-
- @GetMapping("/user/info")
- @PreAuthorize("hasRole('USER')") // 只有 user 角色才能访问
- public SysUserDetails userInfo() {
- // 获取当前登录的用户信息
- SysUserDetails user = (SysUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- log.info("当前登录的用户信息:{}", user.toString());
- return user;
- }
- }
-
复制代码 启动测试
访问 admin/info 接口
访问 user/info 接口
访问 admin/info 接口
访问 user/info 接口
通过上面的测试用例,通过定义用户和角色实体、实现自定义的 UserDetailsService,实现了数据库驱动的用户认证和基于角色的授权机制。这种结合方式不仅在安全性上提供了极大的机动性,也让数据管理变得更加简洁高效。
关注我,在后续的文章中,我们进一步探讨如果使用JWT、OAuth2 等机制、使用Redis作为缓存来强化认证与授权的实现。
示例源码:关注公众号“Harry技术”,回复 security 获取源码地址。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |