idea查询类_Spring Security入门(三): 基于自定义数据库查询的认证实战

0 引言

在笔者的上一篇文章中Spring Security入门(二):基于内存的认证一文中有提到过Spring Security实现自定义数据库查询需要你实现UserDetailsService接口,并实现loadUserByUsername(String username)抽象方法。我们可以在UserDetailsService接口的实现类中注入数据库访问对象Dao,从而实现自定义数据库查询认证用户信息。下面在笔者的boot-demo实战项目中我们结合spring data jpa作为持久层技术来一步一步实现自定义数据库认证。

1 表结构设计与实体类

1.1 新建用户表tbl_user与对应实体类

笔者使用的数据库为mysql5.6, 在IDEA中新建一个客户端连接,并在就控制台窗口中执行如下新建tbl_user表的脚本:

use mysql;

然后执行插入两条数据:

#张三原始密码为zhangsan123

为了维护用户敏感信息的安全,数据库里用户的登录密码或支付密码等安全性要求较高的字段一律采用加密存储的方式存储。

添加用户的sql脚本中用户的加密密文均在是IDEA中的命令控制台执行spring-boot-cli命令spring encodepassword ${password}的方式获得,其实质是使用BCryptPasswordEncoder编码原始密码所得

tbl_user表的建表依据为:在spring security自定义用户类必须实现UserDetailsUserDetails的源码如下:

public 

于是我们创建一个实现UserDetails接口的实现类并使之与tbl_user表中的字段一一对应

user.java

"tbl_user")

1.2 新建角色表roels及其对应的实体类

roles表的建表sql脚本如下:

use mysql;

执行难添加用户sql脚本:

insert 

新建roles表对应是实体类Role.java:

@Entity(name=

1.3 新建用户-角色关系关系表tbl_user_role及其对应的实体类

tbl_user_role表的建表sql脚本如下:

use mysql;

执行往tbl_user_role表添加数据sql脚本:

--插入数据

新建tbl_user_role表对应的实体类UserRole.java

@Entity(name=

2  用于认证用户信息的数据库访问层

2.1 新建与用户表对应的Repository接口

public 

TblUserRepository接口继承JpaRepository接口,自动拥有了基本的CRUD、分页查询方法及根据字段和关键字查找表对应实体类信息的功能。在TblUserRepository接口中我们自定义了一个根据username字段查找用户信息的方法,继承自JpaRepository接口的数据库访问接口无需开发人员手动实现其中 2.2 新建与角色表对应的Repository接口

public 

RoleRepository接口中笔者自定义了根据角色id列表查询角色列表的抽象方法,方便给用户查询角色列表

2.3 新建与用户角色关系表对应的Repository接口

public 

UserRoleRepository接口中,笔者定义了根据角色id查询用户角色列表的抽象方法。

3 定义UserDetailsService接口的实现类

@Service

以上为了从数据库中查出登录用户的用户名、加密密文及角色列表从数据库中查了3次。

  • 第1次通过TblUserRepository#findUserByUsername传入username参数查出不包含角色信息的User对象,如果用户不存在则直接返回null;

  • 第2次通过UserRoleRepository#findByUserId 传入用户id查出用户-角色关系列表

  • 第3步通过第2部中得到了角色id列表作为入参传入到RoleRepository#findRolesByRoleIdIn方法得到完整的角色信息列表

由于使用spring-data-jpa 实现关联查询笔者暂时还没有掌握,因而以上认证用户信息访问了三次数据库,确实容易影响效率;在实际的商用生产环境可以参照spring-data-jpa的连接查询改为连接查询,对于用户登录认证信息等热点数据首次你从数据库查询出来后最好缓存在redis缓存中,并设置过期时间。另外如果是使用mybatis作为数据库持久层框架,可以借助resultMap集合association属性通过一条sql将包含角色列表的用户信息一次性查出来

4 WebSecurityConfigurerAdapter实现类中配置userDetailsService

4.1 配置userDetailsService

@Configuration

在上文《Spring Security入门(二) 基于内存存储的表单登录实战》的基础上对所有用户进入登录页面和登录接口放开权限,而对/index/*路径下的接口允许访问角色改为数据库中存在的Admin,SystemAdmin,Developer等角色。登录成功处理器和失败处理器配置用沿用上文中的逻辑。

4.2 测试用户登录认证效果

在浏览器中输入 http://localhost:8088/apiBoot/login 回车即可进入登录页面a898a31b3f97de0e296d5f3fe07280b1.png

右键->检查  在下方在弹出的元素审查窗口中选中Elements标签查看表单的html源码,我们可以看到登录表单中实际还包含了一个隐藏了_csrf输入框,其值为622251f2-f7f3-4b78-88a0-52451771deaf,是一个UUID字符串,它的用处是为了保护web请求,防止跨站请求伪造(简称CSRF)

63f55614a03745b21f66cb39cc233755.png

输入数据库中存在的用户账号x_zhangsan己密码zhangsan123,点击Sign in即可登录成功,登录成功后浏览器中会返回如下信息:

"msg":

登录成功后的返回信息中包含了用户的基本属性和角色及权限信息。

使用postman登录需要带上_csrftoken值:

//localhost:8088/apiBoot/login?username=x_zhangsan&password=zhangsan123&_csrf=66dff592-bc63-488f-ab34-929258a55db6

在postman 中响应信息得到了json格式的美化,看起来非常清晰

5  存储用户认证信息类的源码解读

5.1 认识SecurityContextHolderSecurityContext

用户登录成功后的认证信息最终能会作为一个Authentication 实现类对象(表单登录通常对应的是一个UsernamePasswordAuthenticationToken对象)对象被认证过滤器保存在SecurityContextHolder类的SecurityContext(安全上下文)中,之后就可以通过SecurityContextHolder这个类直接去获取当前登录用户的认证信息了,SecurityContextHolder其实就是一个存放用户具体认证信息的工具类。

通过查看这两个类的相关源码可以对Spring Security安全框架是如何保存用户的认证信息的原理会有一个更全面的认识,相关源码如下:

SecurityContextHolder.java

public 

SecurityContext.java

public 

SecurityContext类是一个接口,它的实现类是SecurityContextImpl

(1)利用SecurityContextHolder保存用户认证信息的示例源码:

//第1步创建一个空的SecurityContext对象实例
  • 采用SecurityContextHolder.createEmptyContext()方法,而不是使用SecurityContextHolder.getContext().setAuthentication(authentication) 获取一个SecurityContext对象实例的目的是为了避免多线程场景下的跨线程竞争

  • Spring Security不关心是何种Authentication的实现类实例被设置到SecurityContext实例中,测试场景下使用TestingAuthenticationToken实现类是为了方便,大多数生产场景下一般选用UsernamePasswordAuthenticationToken(userDetails, password, authorities)构造Authentication实现类对象实例

  • 最后SecurityContext实例被保存到SecurityContextHolder类后,Spring Security会使用这些信息来进行后面当前认证用户在每一个限权操作的权限鉴定,简称鉴权(authorization)

(2)利用SecurityContextHolder获取用户的认证信息和权限的代码实例如下:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection extends GrantedAuthority> authorities =
authentication.getAuthorities();

默认情况下,SecurityContextHolder使用一个ThreadLocal对象用于存储用户的详细认证信息,这也就意味着即时当前SecurityContext对象没有作为一个参数传递到具体的方法里去,同一个线程中的任意方法都能拿到SecurityContext对象,进而拿到用户的认证信息。如果在当前主体的请求被处理后清除线程程,以这种方式使用ThreadLocal是非常安全的。Spring Security FilterChainProxy(过滤链代理)确保了 SecurityContext永远是干净的。

5.2 认识SecurityContextHolder类中的SecurityContextHolderStrategy

由于一些应用与线程特殊的工作方式,并非所有的应用都完全适合使用ThreadLocal对象来存储安全上下文。例如对于一个Swing客户端应用就要求虚拟机种所有线程共享一个安全上下文对象,这种情况修啊需要选择全局策略。

SecurityContextHolder一共由三种方式存储SecurityContext对象,可以通过在应用启动前调用方法SecurityContextHolder.setStrategyName(String strategyName)方法进行设置。三种策略模式分别是

  • SecurityContextHolder.MODE_GLOBAL: 全局模式,适用于单个应用要求虚拟机中所有线程要求共享一个安全上下文的场景
  • SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:继承本地线程模式
  • SecurityContextHolder.MODE_THREADLOCAL:  本地线程模式

SecurityContextHolder.java

public 

通过阅读SecurityContextHolder类中的关键源码,可以看出SecurityContextHolder类首先通过系统变量名spring.security.strategy从系统属性中获取strategyName,并在初始化方法中根据strategyName去实例化strategy属性。在初始化方法中,首先判断strategyName变量是否为空,为空的化就使用MODE_THREADLOCAL模式,然后根据strategyName的值去构建不同的SecurityContextHolderStrategy实现类实例。MODE_THREADLOCAL模式对应ThreadLocalSecurityContextHolderStrategy类实例;MODE_INHERITABLETHREADLOCAL模式对应InheritableThreadLocalSecurityContextHolderStrategy类实例;MODE_GLOBAL对应GlobalSecurityContextHolderStrategy类实例。而SecurityContextHolder类的三个重要的静态方法getContext、setContext和createEmptyContext其实都是委托给strategy来操作的。

通过阅读ThreadLocalSecurityContextHolderStrategy类的源码,我们也可以看到SecurityContext确实是保存在了一个ThreadLocal对象中的泛型变量中。

final 

大多数应用场景下,我们无需改变默认的strategyName的值,默认使用ThreadLocal存储当前登录用户的认证信息即可。

本文代码晚点我会提交到gitee 个人仓库,地址:https://gitee.com/heshengfu1211/boot-demo.git

感兴趣的小伙伴可以克隆下来参考完整的代码

6 参考文章

[1] https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#servlet-authentication

7 推荐阅读

[1] Spring Security 入门(一)Spring Security中的认证与密码编码器

[2] Spring Security入门(二) 基于内存存储的表单登录实战

[3] SpringBoot之路(二)使用用Spring-Data-JPA访问数据库进行基本的CRUD操作

[4] SpringBoot之路(四)Spring-Data-Jpa中的高级应用

初次阅读作者文章的读者欢迎点击文章标题下方的蓝色字体“码农的进阶之路2020”或者扫描下方二维码加个关注,笔者会定期更新java后端与web前端的记技术文干货文章。

658a10b0313eaeee8d2e5bace63bed86.png

读者对本文有任何疑问可在下面的留言板中留言,我看到后会及时回复                      本文留言讨论区  

                      ---END---

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

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

相关文章

python计算小数点后有几位_python的数字类型

python的数字类型分为三种,分别是整数int、 浮点数float 和 复数complex。数字是由数字字面值或内置函数与运算符的结果来创建的, 不带修饰的整数字面值会生成整数。包含小数点或幂运算符的数字字面值会生成浮点数。在数字字面值末尾加上 j 或 J 会生成虚…

Window服务的创建与删除

Windows服务应用程序是一种需要长期运行的应用程序,它对于服务器环境特别适合。它没有用户界面,并且也不会产生任何可视输出。任何用户消息都会被写进Windows事件日志。计算机启动时,服务会自动开始运行。它们不要用户一定登录才运行&#xf…

python颜色识别_颜色检测python

广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 版权声明:本文为博主原创文章,未经博主允许不得转载。 https:blog.csdn.netu0121626…

LSGO软件技术团队与信息1402班开展真人CS活动

LSGO软件技术团队成立于2010年10月,主要从事的应用方向为互联网与移动互联网(UI设计,前端开发,后台开发),地理信息系统;研究方向为数据分析与计算机视觉。成立几年来为学校培养了一批优秀学生&a…

写让别人能读懂的代码

随着软件行业的不断发展,历史遗留的程序越来越多,代码的维护成本越来越大,甚至大于开发成本。而新功能的开发又常常依赖于旧代码,阅读旧代码所花费的时间几乎要大于写新功能的代码。 我前几天看了一本书,书中有这么一句…

非阻IO与EWOULDBLOCK EAGAIN

非阻塞读写 默认 socket 是阻塞的,读写函数 read, readv, recv, recvfrom, recvmsg 以及 write, writev, send, sendto, sendmsg 都有可能会阻塞。可以将 socket 描述字设为非阻塞,这样,当 socket 描述字未就绪时,调用以上读写函…

一起学windows phone7开发(二十一.二 Map控件的简单使用)

1. 注册地图&#xff1a; 在使用地图之前必须先申请register key https://www.bingmapsportal.com/ 将申请到的key填到这个属性&#xff0c;地图才可以正常使用。 CredentialsProvider 属性&#xff1a;填写申请到的Register key。 2.设置中心点&#xff1a; <my:Map Height…

UML类图五种关系与代码的对应关系

UML类图中的五种关系的耦合强弱比较&#xff1a;依赖<关联<聚合<组合<继承 一、依赖关系&#xff1a; &#xff08;一&#xff09;说明 虚线箭头 可描述为&#xff1a;Uses a 依赖是类的五种关系中耦合最小的一种关系。 因为在生成代码的时候&#xff0c;这两个关系…

使用 rapidxml 做配置文件

对于配置文件&#xff0c;一般会选用ini,xml 等等的配置格式。如何快速高效的从文件内读取自己想要的信息是每个做配置文件想要达到的效果。对以小型开发我们并不用时用到msxml这种重量级的解析器。那样会给自己添麻烦的。这里我推荐大家使用rapidxml。 之前使用tinyxml 感觉还…

水晶报表实现(一)

WINFORM下创建水晶报表&#xff1a; 1、新建一个“windows应用程序” 2、添加一个数据集&#xff08;.xsd&#xff09;文件&#xff0c;它是ADO.NET数据集&#xff0c;数据集用于在断开缓存中存储数据&#xff0c;它的结构类似于关系数据库的接口&#xff0c;它公开表、行和列的…

Java swing 实现下拉框和文本框同步显示

想要MyEclipse中的swing中实现下拉框和文本框实现&#xff0c;对下拉框创建MouseEvent、ItemEvent、ActionEvent private void xingbieMouseClicked(java.awt.event.MouseEvent evt) { // TODO add your handling code here: setSelectedItem(evt, this.xingbie1); } private v…

python image 转成字节_就是这么流弊!三行Python代码,让数据处理速度提高2到6倍

选自TowardsDataScience作者&#xff1a;George Seif本文转自机器之心(nearhuman2014)本文可以教你仅使用 3 行代码&#xff0c;大大加快数据预处理的速度。Python 是机器学习领域内的首选编程语言&#xff0c;它易于使用&#xff0c;也有很多出色的库来帮助你更快处理数据。但…

LSGO软件技术团队内部技术交流【2015-2016(1)第七周】

LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养了一批优秀学生&a…

python beautiful soup 标签完全相同_Python爬取Python教程并制作成pdf

欢迎点击右上角关注小编&#xff0c;除了分享技术文章之外还有很多福利&#xff0c;私信学习资料可以领取包括不限于Python实战演练、PDF电子文档、面试集锦、学习资料等。想要把教程变成PDF有三步&#xff1a;1、先生成空html&#xff0c;爬取每一篇教程放进一个新生成的div&a…

LSGO软件技术团队2015~2016学年第八周(1019~1025)总结

LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养了一批优秀学生&a…

ENVI计算公式(一)

<1>大于1的值赋予1&#xff0c;小于0的值赋予0 ((b1 lt 0)*(0)(b1 ge 0)*b1)or((b1 gt 1)*(1)(b1 ge 0)*b1) <2>modis数据计算ndvi b1*0.0001 <3>modis数据计算地表温度&#xff08;单位&#xff1a;摄氏度&#xff09; b1*0.02-273.15 <4>modis数据…

list取数据_Day.5利用Pandas做数据处理(二)

数据合并使用Join()合并&#xff0c;合并的方式是根据行和行进行合并。# 使用join合并&#xff0c;着重关注的是 行的合并import pandas as pd df1pd.DataFrame({Red:[1,3,5],Green:[5,0,3]},indexlist(abc))df2pd.DataFrame({Blue:[1,9,8],Yellow:[6,6,7]},indexlist(cde))pri…

MRP的数据处理-华北水利水电大学(作业)

这是之前学经济方向的同学让我利用C语言写的关于MRP的数据处理的过程&#xff0c;在用C语言写的过程中利用了动态数组使得时区不仅限于这8时区&#xff0c;有相关的同学可以进行查看 代码运行结果如下&#xff08;该代码可以自动调整参数&#xff0c;时区可以设置8天以上&#…

LSGO软件技术团队2015~2016学年第九周(1026~1101)总结

简述&#xff1a; LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养…

git 每次都要输入用户名密码_Git向GitHub提供代码

一.前期准备工作首先建立一个GitHub账号&#xff0c;这个账号和密码邮箱要记住&#xff0c;如果忘记了也可以找回&#xff0c;会麻烦一些。在官网下载一个Git,可以自己根据默认进行安装&#xff0c;这样也是没有问题的&#xff0c;如果系统盘的空间不够大&#xff0c;可以安装到…