Spring Boot 3 集成 Spring Security(3)数据管理

news2024/11/29 3:02:45

文章目录

    • 准备工作
      • 新建项目
      • 引入MyBatis-Plus依赖
      • 创建表结构
      • 生成基础代码
    • 逻辑实现
      • `application.yml`配置
      • SecurityConfig 配置
      • 自定义 UserDetailsService
      • 创建测试
    • 启动测试

在前面的文章中我们介绍了 《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;
      }
  }
  

启动测试

  • 1.登录admin账户

访问 admin/info 接口

在这里插入图片描述

访问 user/info 接口

在这里插入图片描述

  • 2.登录user用户

访问 admin/info 接口

在这里插入图片描述

访问 user/info 接口

在这里插入图片描述

通过上面的测试用例,通过定义用户和角色实体、实现自定义的 UserDetailsService,实现了数据库驱动的用户认证和基于角色的授权机制。这种结合方式不仅在安全性上提供了极大的灵活性,也让数据管理变得更加简洁高效。

关注我,在后续的文章中,我们进一步探讨如果使用JWT、OAuth2 等机制、使用Redis作为缓存来强化认证与授权的实现。

示例源码:关注公众号“Harry技术”,回复 security 获取源码地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2249473.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT的应用场景:开启无限可能的大门

ChatGPT的应用场景:开启无限可能的大门 随着人工智能技术的快速发展,自然语言处理领域迎来了前所未有的突破。其中,ChatGPT作为一款基于Transformer架构的语言模型,凭借其强大的语言理解和生成能力,在多个行业和场景中展现出了广泛的应用潜力。以下是ChatGPT八个最具代表…

13 —— 开发环境调错-source map

问题&#xff1a;代码被压缩后&#xff0c;无法正确定位源代码的位置&#xff08;行数和列数&#xff09; source map&#xff1a;准确追踪error和warning在原始代码的位置 —— webpack.config.js配置devtool选项 module.exports { devtool: inline-source-map }; inline-s…

水库大坝安全监测之量水堰计应用

量水堰计是水库大坝安全监测系统中的一种关键设备&#xff0c;主要用于测量水库水位、流量等水力参数。以下是量水堰计在水库大坝安全监测中的应用及注意事项&#xff1a; 一、量水堰计的工作原理 量水堰计是一种专门用于测量水流流量的仪器&#xff0c;其工作原理主要基于水流…

Scrapy图解工作流程-cnblog

1.1 介绍部分&#xff1a; 文字提到常用的Web框架有Django和Flask&#xff0c;接下来将学习一个全球范围内流行的爬虫框架Scrapy。 1.2 内容部分&#xff1a; Scrapy的概念、作用和工作流程 Scrapy的入门使用 Scrapy构造并发送请求 Scrapy模拟登陆 Scrapy管道的使用 Scrapy中…

复合查询和内外连接

文章目录 1. 简单查询2. 多表查询2.1 显示雇员名、雇员工资以及所在部门的名字2.2 显示部门号为10的部门名&#xff0c;员工名和工资2.3 显示各个员工的姓名&#xff0c;工资&#xff0c;及工资级别 3. 自连接4. 子查询4.1 where后的子查询4.1.1 单行子查询4.1.2 多行子查询 (i…

UniApp开发实战:常见报错解析与解决方案

UniApp开发实战&#xff1a;常见报错解析与解决方案 病例1、TypeError: undefined is not an object (evaluating ‘this. s c o p e . scope. scope.getAppWebview’) 需求&#xff1a;获取页面示例&#xff0c;动态修改头部搜索框内容&#xff0c;获取页面实例时候报错unde…

Docker 容器网络创建网桥链接

一、网络&#xff1a;默认情况下&#xff0c;所有的容器都以bridge方式链接到docker的一个虚拟网桥上&#xff1b; 注意&#xff1a;“172.17.0.0/16”中的“/16”表示子网掩码的长度为16位&#xff0c;它表示子网掩码中有16个连续的1&#xff0c;后面跟着16个连续的0。用于区分…

一个开源轻量级的服务器资源监控平台,支持告警推送

大家好&#xff0c;今天给大家分享一款开源的轻量级服务器资源监控工具Beszel&#xff0c;提供历史数据记录、Docker容器统计信息监控以及多种警报功能&#xff0c;用于监控服务器资源。 项目介绍 Beszel由hub&#xff08;中心服务器端应用&#xff0c;基于PocketBase构建&…

使用Compose Multiplatform开发跨平台的Android调试工具

背景 最近对CMP跨平台很感兴趣&#xff0c;为了练手&#xff0c;在移动端做了一个Android和IOS共享UI和逻辑代码的天气软件&#xff0c;简单适配了一下双端的深浅主题切换&#xff0c;网络状态监测&#xff0c;刷新调用振动器接口。 做了两年多车机Android开发&#xff0c;偶…

[MRCTF2020]Transform

查壳&#xff0c;拖入64位IDA LOBYTE8位就是一个字节&#xff0c;在此处无意义&#xff0c;因为我们输入的本来就是按字节输入的 设 a byte_414040,bdword_40F040,cbyte_40F0E0,输入的字符串为flag; 从题目里得到 加密代码 a[i] flag[b[i]]; a[i] ^ b[i]; c a 即c[i] a[i…

podman 源码 5.3.1编译

1. 构建环境 在麒麟V10服务器操作系统上构建&#xff1a;Kylin-Server-V10-GFB-Release-2204-Build03-ARM64.iso。由于只是编译 podman 源码&#xff0c;没必要特地在物理机或服务上安装一个这样的操作系统&#xff0c;故采用在虚拟机里验证。 2. 安装依赖 参考资料&#xf…

Llmcad: Fast and scalable on-device large language model inference

题目&#xff1a;Llmcad: Fast and scalable on-device large language model inference 发表于2023.09 链接&#xff1a;https://arxiv.org/pdf/2309.04255 声称是第一篇speculative decoding边缘设备的论文&#xff08;不一定是绝对的第一篇&#xff09;&#xff0c;不开源…

用Java爬虫“搜刮”工厂数据:一场数据的寻宝之旅

引言&#xff1a;数据的宝藏 在这个数字化的时代&#xff0c;数据就像是隐藏在数字丛林中的宝藏&#xff0c;等待着勇敢的探险家去发掘。而我们&#xff0c;就是那些手持Java魔杖的现代海盗&#xff0c;准备用我们的爬虫船去征服那些数据的海洋。今天&#xff0c;我们将一起踏…

14、保存与加载PyTorch训练的模型和超参数

文章目录 1. state_dict2. 模型保存3. check_point4. 详细保存5. Docker6. 机器学习常用库 1. state_dict nn.Module 类是所有神经网络构建的基类&#xff0c;即自己构建一个深度神经网络也是需要继承自nn.Module类才行&#xff0c;并且nn.Module中的state_dict包含神经网络中…

【计算机网络】多路转接之poll

poll也是一种linux中的多路转接方案(poll也是只负责IO过程中的"等") 解决&#xff1a;1.select的fd有上限的问题&#xff1b;2.每次调用都要重新设置关心的fd 一、poll的使用 int poll(struct pollfd *fds, nfds_t nfds, int timeout); ① struct pollfd *fds&…

矩阵重新排列——sort函数

s o r t sort sort函数表示排序&#xff0c;对向量和矩阵都成立 向量 s o r t ( a ) sort(a) sort(a)将向量 a a a中元素从小到大排序 s o r t ( a , ′ d e s c e n d ′ ) sort(a,descend) sort(a,′descend′)将向量 a a a中元素从大到小排序 [ s o r t a , i d ] s o r…

深入解密 K 均值聚类:从理论基础到 Python 实践

1. 引言 在机器学习领域&#xff0c;聚类是一种无监督学习的技术&#xff0c;用于将数据集分组成若干个类别&#xff0c;使得同组数据之间具有更高的相似性。这种技术在各个领域都有广泛的应用&#xff0c;比如客户细分、图像压缩和市场分析等。聚类的目标是使得同类样本之间的…

Leetcode322.零钱兑换(HOT100)

链接 代码&#xff1a; class Solution { public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount1,amount1);//要兑换amount元硬币&#xff0c;我们就算是全选择1元的硬币&#xff0c;也不过是amount个&#xff0c;所以初始化amoun…

【61-70期】Java面试题深度解析:从集合框架到线程安全的最佳实践

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Java &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 文章题目&#xff1a;Java面试题深度解析&#xff1a;从集合框架到线程安全的最佳实践 摘要&#xff1a; 本…

关闭AWS账号后,服务是否仍会继续运行?

在使用亚马逊网络服务&#xff08;AWS&#xff09;时&#xff0c;用户有时可能会考虑关闭自己的AWS账户。这可能是因为项目结束、费用过高&#xff0c;或是转向使用其他云服务平台。然而&#xff0c;许多人对关闭账户后的服务状态感到困惑&#xff0c;我们九河云和大家一起探讨…