3.SpringSecurity基于数据库的认证与授权

文章目录

  • SpringSecurity基于数据库的认证与授权
  • 一、自定义用户信息UserDetails
    • 1.1 新建用户信息类UserDetails
    • 1.2 UserDetailsService
  • 二、基于数据库的认证
    • 2.1 连接数据库
    • 2.2 获取用户信息
      • 2.2.1 获取用户实体类
      • 2.2.2 Mapper
      • 2.2.3 Service
    • 2.3 认证
      • 2.3.1 实现UserDetails接口
      • 2.3.2 实现UserDetailsService接口
      • 2.3.3 安全配置类
      • 2.3.4 测试程序
        • 2.3.4.1 控制器
        • 3.3.4.1 访问
  • 三、基于数据库的授权
    • 3.1 数据库表
    • 3.2 获取权限
      • 3.2.1 权限类 SysMenu
      • 3.2.2 SysMenuDao
      • 3.2.3 SysMenuServiceImpl
      • 3.2.4 修改 UserDetailsService
      • 3.2.5 修改 UserDetails
    • 3.3 测试
  • 四、总结

SpringSecurity基于数据库的认证与授权

承接:2.SpringSecurity - 处理器简单说明-CSDN博客

我们之前学习的用户的信息都是配置在代码中,如下段代码所示

/*** 定义一个Bean,用户详情服务接口* <p>* 系统中默认是有这个UserDetailsService的,也就是默认的用户名(user)和默认密码(控制台生成的)* 如果在yaml文件中配置了用户名和密码,那在系统中的就是yaml文件中的信息* <p>* 我们自定义了之后,就会把系统中的UserDetailsService覆盖掉*/
@Configuration
public class MySecurityUserConfig {/*** 根据用户名把用户的详情从数据库中获取出来,封装成用户细节信息UserDetails(包括用户名、密码、用户所拥有的权限)* <p>* UserDetails存储的是用户的用户名、密码、去权限信息*/@Beanpublic UserDetailsService userDetailsService() {
//      用户细节信息,创建两个用户
//      此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456"))
//               配置用户角色.roles("student") //角色到系统中会变成权限的,比如这里会变成ROLE_student,ROLE_manager.build();UserDetails user2 = User.builder().username("zhangjingqi-2").password(passwordEncoder().encode("123456"))
//              配置权限.authorities("teacher:query").build();UserDetails user3 = User.builder().username("admin").password(passwordEncoder().encode("123456"))
//              配置权限.authorities("teacher:query","teacher:add","teacher:update","teacher:delete").build();//      InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);userDetailsManager.createUser(user3);return userDetailsManager;}/*** 配置密码加密器* NoOpPasswordEncoder.getInstance() 此实例表示不加密* BCryptPasswordEncoder() 会加密*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

但是我们并不希望上面这样写,我们希望把用户的信息存入到数据库

一、自定义用户信息UserDetails

用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图

img

1.1 新建用户信息类UserDetails

//只有当"accountNonExpired"、“accountNonLocked”、“credentialsNonExpired”、"enabled"都为true时,账户才能使用
//之前我们创建的时候,直接User.builder()创建,之后InMemoryUserDetailsManager对象createUser
public class SecurityUser implements UserDetails {/*** @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {//明文为123456return "$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq";}/*** @return 用户名*/@Overridepublic String getUsername() {return "thomas";}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return true;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return true;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return true;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return true;}
}

1.2 UserDetailsService

/*** 当我们定义了此类后,系统默认的UserDetailsService不会起作用,下面UserServiceImpl会起作用*/
@Service
public class UserServiceImpl implements UserDetailsService {/*** 根据用户名获取用户详情UserDetails*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SecurityUser securityUser= new SecurityUser();//我们自己定义的if(username==null || !username.equals(securityUser.getUsername())){
//          SpringSecurity框架中自带的异常throw new UsernameNotFoundException("该用户不存在或用户名不正确");}
//      执行到这里,说明username是没有问题的
//      用户密码对不对,框架会帮我们进行判断return securityUser;}}

二、基于数据库的认证

我们观察到在1.1UserDetails中,我们把用户名和密码是写死的,但是这种情况下是不合理的,包括权限在内,我们都需要从数据库中取出来

将信息从数据库取出来后,可以将信息封装成一个UserDetails类

image-20231019221142173

2.1 连接数据库

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.15</version>
</dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version>
</dependency>
spring:#数据源datasource:#德鲁伊连接池druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springsecurity?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: rootmybatis:#SQL映射文件的位置mapper-locations: classpath:mapper/**/*.xml# 指定实体类起别名,(实体类所在的包的包路径,那么包中的所有实体类别名就默认是类名首字母小写)type-aliases-package: com.zhangjingqi.entityconfiguration:#开启驼峰命名法map-underscore-to-camel-case: true#日志功能log-impl: org.apache.ibatis.logging.stdout.StdOutImpl     

2.2 获取用户信息

判断用户是否正确,我们可以从sys_user中取出用户相关信息,将用户的相关信息取出来后封装成一个UserDetails类

image-20231020091944626

image-20231020101927297

2.2.1 获取用户实体类

获取用户信息的实体类,这里不建议SysUser实体类实现UserDetails接口,因为会显得很乱

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SysUser implements Serializable {private static final long serialVersionUID = -5352627792860514242L;private Integer userId;private String username;private String password;private String sex;private String address;private Integer enabled;private Integer accountNoExpired;private Integer credentialsNoExpired;private Integer accountNoLocked;}

2.2.2 Mapper

封装dao层,也就是Mapper层,从数据库中获取用户的信息

@Mapper
public interface SysUserDao {/*** 根据用户名访问用户信息* @param userName 用户名* @return 用户信息*/SysUser getByUserName(@Param("userName") String userName);}
<mapper namespace="com.zhangjingqi.dto.SysUserDao"><select id="getByUserName" resultType="com.zhangjingqi.entity.SysUser">select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_lockedfrom sys_user where username = #{userName};</select></mapper>

2.2.3 Service

@Slf4j
@Service
public class SysUserServiceImpl implements SysUserService {@Autowiredprivate SysUserDao sysUserDao;@Overridepublic SysUser getByUserName(String userName) {return sysUserDao.getByUserName(userName);}
}

2.3 认证

2.3.1 实现UserDetails接口

我们之前是写死的用户信息,但是现在是从数据库中进行获取的

@Data
public class SecurityUser implements UserDetails {private static final long serialVersionUID = -1314948905954698478L;private final SysUser sysUser ;public SecurityUser(SysUser sysUser) {this.sysUser = sysUser;}/*** @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {return sysUser.getPassword();}/*** @return 用户名*/@Overridepublic String getUsername() {return sysUser.getUsername();}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired() != 0;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked() !=0;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired() !=0 ;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return sysUser.getEnabled() !=0 ;}
}

这个地方我们之前是这么写的

image-20231020110240997

2.3.2 实现UserDetailsService接口

@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//      1.从数据库获取用户的详情信息SysUser sysUser = sysUserService.getByUserName(username);if (null == sysUser){
//          这个异常信息是SpringSecurity中封装的throw new UsernameNotFoundException("用户没有找到");}//      2.封装成UserDetails类,SecurityUser类实现了UserDetails接口SecurityUser securityUser = new SecurityUser(sysUser);return securityUser;}
}

我们之前是这么写的

这篇文章会有介绍:1.SpringSecurity -快速入门、加密、基础授权-CSDN博客

image-20231020105535812

2.3.3 安全配置类

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest()//任何请求.authenticated();//需要验证http.formLogin().permitAll(); //SpringSecurity的表单认证}/*** @return 密码加密器*/@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}}

2.3.4 测试程序

2.3.4.1 控制器
@RestController
@RequestMapping("/student")
public class StudentController {@GetMapping("/query")private String query(){return "query student";}@GetMapping("/add")private String add(){return "add student";}@GetMapping("/delete")private String delete(){return "delete student";}@GetMapping("/update")private String update(){return "export student";}
}
@RestController
@RequestMapping("/teacher")
public class TeacherController {@GetMapping("/query")@PreAuthorize("hasAuthority('teacher:query')")//预授权public String queryInfo() {return "teacher query";}}
3.3.4.1 访问

用下面这个人的信息进行登录

image-20231020140635419

访问一下localhost:8080/student/query,发现可以访问

image-20231020142807340

再访问一下localhost:8080/teacher/query,发现是不能访问的,原因是thomas用户没有/teacher/query路径的权限

image-20231020142907797

三、基于数据库的授权

3.1 数据库表

首先我们很明确的就是用户和角色的关系是多对多的

用户表

image-20231020150336382

image-20231020150438581

角色表

image-20231020150353283

image-20231020150456440

用户与角色关联表

image-20231020150418327

权限表

image-20231021145545952

image-20231021145604961

权限与角色关联表

image-20231021145635383

image-20231021145648250

3.2 获取权限

3.2.1 权限类 SysMenu

@Data
public class SysMenu implements Serializable {private static final long serialVersionUID = 597868207552115176L;private Integer id;private Integer pid;private Integer type;private String name;private String code;
}

3.2.2 SysMenuDao

@Mapper
public interface SysMenuDao {List<String> queryPermissionByUserId(@Param("userId") Integer userId);
}

这个地方涉及到三张表,角色用户关联表sys_role_user、角色权限关联表sys_role_menu、权限表sys_menu

我们要实现通过用户获取对应的权限

<mapper namespace="com.zhangjingqi.dto.SysMenuDao"><select id="queryPermissionByUserId" resultType="java.lang.String">SELECT distinct sm.codeFROM sys_role_user sruinner join sys_role_menu srmon sru.rid = srm.ridinner join sys_menu sm on srm.mid = sm.idwhere sru.uid = #{userId}and sm.delete_flag = 0</select>
</mapper>

3.2.3 SysMenuServiceImpl

@Service
public class SysMenuServiceImpl implements SysMenuService {@Autowiredprivate SysMenuDao sysMenuDao;@Overridepublic List<String> queryPermissionByUserId(Integer userId) {return sysMenuDao.queryPermissionByUserId(userId);}
}

3.2.4 修改 UserDetailsService

@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysMenuService sysMenuService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//      1.从数据库获取用户的详情信息SysUser sysUser = sysUserService.getByUserName(username);if (null == sysUser){
//          这个异常信息是SpringSecurity中封装的throw new UsernameNotFoundException("用户没有找到");}//      2.获取该用户的权限List<String> permissionList = sysMenuService.queryPermissionByUserId(sysUser.getUserId());//        List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(permission -> new SimpleGrantedAuthority(permission) ).collect(Collectors.toList());
//      将集合的泛型转换成SimpleGrantedAuthority (GrantedAuthority类的子类即可)List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());//      2.封装成UserDetails类,SecurityUser类实现了UserDetails接口SecurityUser securityUser = new SecurityUser(sysUser);securityUser.setSimpleGrantedAuthorities(simpleGrantedAuthorities);return securityUser;}
}

3.2.5 修改 UserDetails

@Data
public class SecurityUser implements UserDetails {private static final long serialVersionUID = -1314948905954698478L;private final SysUser sysUser ;//  用户权限private List<SimpleGrantedAuthority> simpleGrantedAuthorities;public SecurityUser(SysUser sysUser) {this.sysUser = sysUser;}/***  这个集合中对象的类型必须是GrantedAuthority类或其子类* @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return simpleGrantedAuthorities;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {return sysUser.getPassword();}/*** @return 用户名*/@Overridepublic String getUsername() {return sysUser.getUsername();}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired() != 0;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked() !=0;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired() !=0 ;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return sysUser.getEnabled() !=0 ;}
}

3.3 测试

使用Obama登录

image-20231021155010195

查看obama权限

image-20231021155118766

四、总结

认证与授权,需要我们实现UserDetails用户详情类和UserDetailsService类

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「林曦的亲子美育」讲讲关于阅读的那些事儿

「林曦的亲子美育」是“林曦的小世界”2023年策划的一档新栏目。林曦老师作为一个“小男生的妈妈”,在这些年分享了许多关于亲子教育的心得&#xff1a;以“美”作为连接和最高标准&#xff0c;会护持着小朋友的选择和人生。教育是一个生活的过程。做一餐饭、读一本书、看一张画…

vm_flutter

附件地址 https://buuoj.cn/match/matches/195/challenges#vm_flutter 可以在buu下载到。 flutter我也不会&#xff0c;只是这个题目加密算法全部在java层&#xff0c;其实就是一个异或和相加。 反编译 package k;import java.util.Stack;/* loaded from: classes.dex */ pu…

p5.js map映射

本文简介 带尬猴&#xff0c;我嗨德育处主任 p5.js 为开发者提供了很多有用的方法&#xff0c;这些方法实现起来可能不难&#xff0c;但却非常实用&#xff0c;能大大减少我们的开发时间。 本文将通过举例说明的方式来讲解 映射 map() 方法。 什么是映射 从 p5.js 文档 中可…

VSCode:清理ipch缓存

VSCode使用了一段时间&#xff0c;发现有些变慢&#xff0c;电脑管家扫描后&#xff0c;提示“AppData\Local\Microsoft\vscode-cpptools\ipch”目录下有很多缓存文件可以清理。 查询了一下&#xff1a;C/C 扩展常见问题解答 (visualstudio.com) 该件夹内包含缓存的预编译头文…

【算法练习Day30】无重叠区间 划分字母区间合并区间

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 无重叠区间划分字母区间合并…

进击的零跑:拿巨头的钱,把中国车打进欧洲市场

作者 | 张祥威 编辑 | 德新 造车新势力中&#xff0c;零跑属于不惹事&#xff0c;独自在角落低调发育的这一类。偶尔高调门喊一声要干掉特斯拉&#xff0c;围观的人也是笑一笑不当回事儿&#xff0c;于是又回去默默卖车。 声量一般&#xff0c;卖车还行。 行业里每次晒数据&…

redis持久化之RDB(Redis DataBase)

1 : 总体介绍 Redis是一个基于内存的数据库&#xff0c;它的数据是存放在内存中&#xff0c;内存有个问题就是关闭服务或者断电会丢 失。 Redis的数据也支持写到硬盘中&#xff0c;这个过程就叫做持久化 1.1 。 Redis提供了2种不同形式的持久化方式。 RDB&#xff08;Redis Da…

【Docker】Docker-Compose内置DNS负载均衡失效问题

Docker Compose实现负载均衡 还是对前面的例子docker-compose.yml稍微修改&#xff1a; version: "3.8"services:flask-demo:build:context: .dockerfile: Dockerfileimage: flask-demo:latestenvironment:- REDIS_HOSTredis-server- REDIS_PASS${REDIS_PASS}healt…

回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测(多指标,多图)

回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测&#xff08;多指标&#xff0c;多图&#…

游戏研发的解决方案有哪些?

游戏研发的解决方案可以根据不同的需求和情境而有所不同&#xff0c;以下是一些常见的游戏研发解决方案&#xff1a; 游戏引擎&#xff1a; 游戏引擎是游戏研发的基础&#xff0c;它提供了开发游戏所需的核心功能&#xff0c;如图形渲染、物理引擎、音效管理、动画等。一些流行…

一、高效构建Java应用:Maven入门和进阶

一、高效构建Java应用&#xff1a;Maven入门和进阶 目录 一、Maven简介和快速入门 1.1 Maven介绍1.2 Maven主要作用理解1.3 Maven安装和配置 二、基于IDEA的Maven工程创建 2.1梳理Maven工程GAVP属性2.2 Idea构建Maven JavaSE工程2.3 Idea构建Maven JavaEE工程2.4 Maven工程项…

LVS集群-NAT模式

集群的概念&#xff1a; 集群&#xff1a;nginx四层和七层动静分离 集群标准意义上的概念&#xff1a;为解决特定问题将多个计算机组合起来形成一个单系统 集群的目的就是为了解决系统的性能瓶颈。 垂直扩展&#xff1a;向上扩展&#xff0c;增加单个机器的性能&#xff0c;…

KV STUDIO的安装与实践(一)

目录 什么是KV STUDIO&#xff1f; 如何安装KV STUDIO&#xff1f; 如何学习与使用KV STUDIO&#xff08;在现实中的应用&#xff09;&#xff1f; 应用一&#xff08;在现实生活中机器内部plc的读取与替换&#xff09; 读取 KV STUDIO实现显示器的检测&#xff01;&#…

Keepalived

一、Keepalived概述 1.1 Keepalived定义 Keepalived为LVS应运而生的高可用服务。由于LVS的调度器无法做高可用&#xff0c;于是引入keepalived软件。实现的是调度器的高可用。 但是keepalived不是专门为lvs集群服务的&#xff0c;也可以做其他代理服务器的高可用。 1.2 LVS…

kubeadm安装k8s集群

目录 一、环境部署 1、关闭防火墙规则、关闭selinux、关闭swap交换分区 2、修改主机名、DNS解析 3、调整内核参数 二、所有节点安装Docker 三、安装k8s集群 1、所有节点配置K8S源 2、所有节点安装kubeadm、kubelet和kubectl 3、部署K8S集群 3.1 初始化操作&#xff08…

车载音频ADI-ADSP21569音频DSP开发

车载音频ADI-ADSP21489音频DSP开发 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送蓝牙音频,车载DSP音频项目核心开发资料, 1 芯片手册 2 电路原理图

网工内推 | 急招网工,思科、华为认证优先,法定节假日三薪

01 江苏臻云技术 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责落实数据中心机房日常网络监测及巡检任务&#xff1b; 2、负责数据中心网络设备日常监控、变更、维护、巡检&#xff1b; 3、负责日常巡检报告、故障维护报告、变更申请的文档的编制&#xff1b;…

竞赛 深度学习人体跌倒检测 -yolo 机器视觉 opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的人体跌倒检测算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满…

番外8.2 --- 后续

### 01&#xff1a;dd命令&#xff1a;在新挂载点创建swap文件大小10MB&#xff1b;&#xff08;dd if/dev/zero of/swap bs1024 count10240&#xff09; 02&#xff1a;给swap建立文件系统&#xff0c;将其分属到swap文件&#xff08;mkswap /swap&#xff1b; swapon /swap &…

21.2 Python 使用Scapy实现端口探测

Scapy 是一款使用纯Python编写的跨平台网络数据包操控工具&#xff0c;它能够处理和嗅探各种网络数据包。能够很容易的创建&#xff0c;发送&#xff0c;捕获&#xff0c;分析和操作网络数据包&#xff0c;包括TCP&#xff0c;UDP&#xff0c;ICMP等协议&#xff0c;此外它还提…