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,一经查实,立即删除!

相关文章

vxworks点滴记录

MPC852---大端模式 对于vxWork5.5来说: vxWorks操作系统是一个进程,其间的所有任务是该进程内的线程,因为他们有统一的地址空间。没有main函数。。 对于vxWorks6来说: 你可选支持多个进程,每个进程由独立的地址空间&…

第4章操作系统基础第五版Aimin.rar

第4章操作系统基础第五版Aimin.rar转载于:https://www.cnblogs.com/emanlee/archive/2010/10/14/1851101.html

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

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

c# 判断点是否在区域内 点在区域内 在多边形内 判断

方法一 算法 : public int isLeft(Point P0, Point P1,Point P2) { int abc ((P1.X - P0.X) * (P2.Y - P0.Y) - (P2.X - P0.X) * (P1.Y - P0.Y)); return abc; } private bool PointInFences(Point pnt1, Point[] fencePnts) { int wn 0,j…

Window服务的创建与删除

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

python颜色识别_颜色检测python

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

涵盖各种编程语言的深度学习库整理大全!

Python 1. Theano是一个python类库,用数组向量来定义和计算数学表达式。它使得在Python环境下编写深度学习算法变得简单。在它基础之上还搭建了许多类库。 1.Keras是一个简洁、高度模块化的神经网络库,它的设计参考了Torch,用Python语言编写&…

epoll机制

在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中&#xf…

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

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

a标签去掉下划线_解决 v-html 元素中标签样式失效 - Vue

最近在做一个项目&#xff0c;是对富文本编辑器生成的 HTML 渲染到页面中&#xff0c;大家都会想到当然是用 Vue 的 v-html 属性&#xff0c;于是我写下了这样的代码<template><div class"content-html" v-html"article.contentHtml" /> </…

判断 Java 中的空字符串

原文地址&#xff1a;http://www.neoease.com/string-is-empty/ 以下是 Java 判断字符串是否为空的三种方法.方法一: 最多人使用的一个方法, 直观, 方便, 但效率很低.方法二: 比较字符串长度, 效率高, 是我知道的最好一个方法.方法三: Java SE 6.0 才开始提供的方法, 效率和方法…

Socket的send函数在执行时报EAGAIN的错误

Socket的send函数在执行时报EAGAIN的错误] 内容提要: 当客户通过Socket提供的send函数发送大的数据包时&#xff0c;就可能返回一个EGGAIN的错误。该错误产生的原因是由于send 函数中的size变量大小超过了tcp_sendspace的值。tcp_sendspace定义了应用在调用send之前能够在k…

写让别人能读懂的代码

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

python中什么是按位取反_Python学习中的“按位取反”笔记总结

|疑惑 最近在学习Python的过程中了解到位运算符&#xff0c;但对于按位取反有点迷糊&#xff0c;就比如说~9&#xff08;按位取反&#xff09;之后的结果是-10&#xff0c;为什么不是6呢&#xff1f;所以下面就来看看为什么不是6&#xff0c;正确结果是如何计算出来的呢&#x…

非阻IO与EWOULDBLOCK EAGAIN

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

一起学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…