关注

最新Spring Security实战教程(五)基于数据库的动态用户认证传统RBAC角色模型实战开发

在这里插入图片描述

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
💋《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

回顾链接:
最新Spring Security实战教程(一)初识Spring Security安全框架
最新Spring Security实战教程(二)表单登录定制到处理逻辑的深度改造
最新Spring Security实战教程(三)Spring Security 的底层原理解析
最新Spring Security实战教程(四)基于内存的用户认证

1. 前言

在上一章节中,我们讲解了 Spring Security 基于内存的用户认证,也提到了实际开发生产中,更多使用的还是基于数据库的动态用户认证 ,因为在企业应用中,用户、角色、权限管理通常都存储在数据库中

本章节博主带着大家以 MySQL 数据库为例,从用户(sys_user)、角色(sys_role)用户角色(sys_user_role)表出发,演示如何使用 Spring Security 动态加载用户信息、角色,实现基于数据库的认证

2. 数据库表结构说明

本文示例基于你整理的 MySQL 表结构,其中主要表结构如下:大家可以根据自己的业务需求进行扩展

  • sys_user:存储用户信息,字段包括 user_id、login_name、password
  • sys_role:存储角色信息,字段包括 role_id、role_name(角色名称)、role_key(角色标识)
  • sys_user_role:关联用户和角色中间表

整体数据库表结构如下:

在这里插入图片描述

以下是为大家整理好的建表语句, 数据库中的用户表:用户名和密码一致

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称',
  `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色权限字符串',
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色信息表';

-- ----------------------------
-- Records of sys_role
-- ----------------------------
BEGIN;
INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`) VALUES (1, '管理员', 'ADMIN');
INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`) VALUES (2, '普通用户', 'USER');
INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`) VALUES (3, '开发者', 'DEVELOPERS');
COMMIT;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `login_name` varchar(30) NOT NULL COMMENT '登录账号',
  `password` varchar(64) DEFAULT NULL COMMENT '登陆密码',
  `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
  `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息表';

-- ----------------------------
-- Records of sys_user
-- ----------------------------
BEGIN;
INSERT INTO `sys_user` (`user_id`, `login_name`, `password`, `status`, `del_flag`) VALUES (1, 'admin', '$2a$10$SnMMruuWQmEEKNMqREDb0e4jfaqJeZviOFjxQRwq.9A7PM6Z0xo5W', '0', '0');
INSERT INTO `sys_user` (`user_id`, `login_name`, `password`, `status`, `del_flag`) VALUES (2, 'user', '$2a$10$96vbFKuEmMlObg1bPqevdOJybTp2cAesJZ5uJBqR797qxnVWx12Wi', '0', '0');
INSERT INTO `sys_user` (`user_id`, `login_name`, `password`, `status`, `del_flag`) VALUES (3, 'developers', '$2a$10$BOpzjv4hmZQB1ydsaDTvZ.Cvyq4.kDty2/ghrcVKhetsTD1sKJaIu', '0', '0');
COMMIT;

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户和角色关联表';

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
BEGIN;
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1, 1);
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1, 3);
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (2, 2);
INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (3, 3);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

3. 完成初始配置

接下来在之前的 Maven 项目中还是创建第四个子模块 db-spring-security

在这里插入图片描述

由于需要操作数据库,在 Maven 主目录pom文件中,追加 mysql数据库驱动 以及 mybatis-plus,博主使用的是 HikariCP 连接池 ,直接引入 spring-boot-starter-data-jpa 即可,Spring官方默认支持

		<!--Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--使用 HikariCP 连接池-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.9</version>
        </dependency>

最后配置yml文件,运行项目确保项目能正常链接数据库且启动成功

server:
  port: 8083
  
spring:
  application:
    name: db-spring-security #最新Spring Security实战教程(五)基于数据库的动态用户认证
  datasource:
    url: jdbc:mysql://localhost:3306/slave_db?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 5

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true # 开启驼峰转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL
    cache-enabled: true # 开启二级缓存
  global-config:
    db-config:
      logic-delete-field: delFlag # 逻辑删除字段
      logic-delete-value: 1 # 删除值
      logic-not-delete-value: 0 # 未删除值

4. MyBatis-Plus实体定义

在确保数据库能正常链接后,接下来就跟着博主一起编写我们的业务代码吧

❶ 用户实体(实现UserDetails)

@Data
@TableName("sys_user")
public class SysUser implements UserDetails {

    @TableId(type = IdType.AUTO)
    private Long userId;
    
    @TableField("login_name")
    private String username; // Spring Security认证使用的字段
    
    private String password;
    private String status;
    private String delFlag;
    
    @TableField(exist = false)
    private List<SysRole> roles;

    // 实现UserDetails接口
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
                .flatMap(role -> role.getMenus().stream())
                .map(menu -> new SimpleGrantedAuthority(menu.getPerms()))
                .collect(Collectors.toList());
    }

    @Override
    public boolean isAccountNonExpired() { return true; }

    @Override
    public boolean isAccountNonLocked() { 
        return "0".equals(status); 
    }

    @Override
    public boolean isCredentialsNonExpired() { return true; }

    @Override
    public boolean isEnabled() { 
        return "0".equals(delFlag); 
    }
}

❷ 角色实体

@Data
@TableName("sys_role")
public class SysRole {
    @TableId(type = IdType.AUTO)
    private Long roleId;
    private String roleName;
    private String roleKey;
}

5. MyBatis-Plus Mapper配置

完成实体类的创建,我们开始配置Mapper实现数据库的业务查询处理,为了快速演示,博主整理就不构建mapper.xml了,直接在Mapper上写查询SQL

UserMapper接口 : 主要是通过用户角色中间表获取角色信息

@Mapper
public interface UserMapper extends BaseMapper<SysUser> {

    @Select("SELECT r.* FROM sys_role r " +
            "JOIN sys_user_role ur ON r.role_id = ur.role_id " +
            "WHERE ur.user_id = #{userId}")
    List<SysRole> selectRolesByUserId(Long userId);
}

6. 自定义UserDetailsService实现

自定义 UserDetailsService 继承 UserDetailsService,重写 loadUserByUsername 方法,注入 UserMapper 通过用户名查询数据库数据,同时将用户的角色集合一并赋值;

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 1. 查询基础用户信息
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getUsername, username);
        SysUser user = userMapper.selectOne(wrapper);

        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 2. 加载角色
        List<SysRole> roles = userMapper.selectRolesByUserId(user.getUserId());
        user.setRoles(roles);

        // 3. 检查账户状态
        if (!user.isEnabled()) {
            throw new DisabledException("用户已被禁用");
        }

        return user;
    }
}

7. Spring Security配置文件与测试

@Configuration
@RequiredArgsConstructor
public class DbSecurityConfig {

    private final UserDetailsServiceImpl userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.
                authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/admin/**").hasAnyAuthority("ADMIN") //测试验证ADMIN角色方可访问
                        .anyRequest().authenticated())
                        .userDetailsService(userDetailsService)
                        .formLogin(withDefaults())
                        .logout(withDefaults())
                ;
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

最后编写测试Controller,当用户登陆认证成功,则默认返回当前用户数据信息

@RestController
@RequiredArgsConstructor
public class DemoDbController {

    @GetMapping("/admin/view")
    public ResponseEntity<String> admin() {
        return ResponseEntity.ok("管理员ROLE_ADMIN角色访问ok");
    }

    @GetMapping("/")
    public ResponseEntity<SysUser> index(Authentication authentication) {
        SysUser principal = (SysUser)authentication.getPrincipal();
        return ResponseEntity.ok(principal);
    }
}

最终完整代码结构如下:小伙伴们可以根据自己需求构建项目测试

在这里插入图片描述

启动项目,在 Spring Security 提供的默认登陆页中输入账号密码,进行登陆!默认返回当前登陆用户信息

在这里插入图片描述

并切换不同的用户进行登陆测试访问 /admin/view ,以确定只有ADMIN角色的用户可以访问!

总结

本章节演示了使用 MyBatis-Plus 进行数据库操作,动态加载用户、角色权限,并将其转换为 Spring SecurityGrantedAuthority 列表,从而实现基于数据库的动态用户认证。并提供了一个简单的 Controller 用于测试和查看当前登录用户的信息。

至此关于Spring Security身份认证的 基于内存 以及 基于数据库 的两种方式均讲解完了,下一个章节我们要进入授权Authorization的相关讲解,小伙伴们耐心等待…

如果本本章内容对您有所帮助,希望 一键三连 给博主一点点鼓励,如果您有任何疑问或建议,请随时留言讨论!


下一章:最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发

在这里插入图片描述

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/lhmyy521125/article/details/146305102

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--