Spring Security学习(六)——配置多个Provider(存在两种认证规则)
前言
《Spring Security学习(五)——账号密码的存取》一文已经能满足一般应用的情况。但实际商业应用也会存在如下的情况:用户提交的账号密码,能在本地的保存的账号密码匹配上,或者能在远端服务认证中匹配上,就算认证通过。这种通常类似于单点登录,或者认证中心之类的。本文不去探索这种架构,只是单纯讨论如果存在两种认证规则下如何处理。
多种认证方式使用Spring Security时有好几种处理方式。根据不同的情况,架构师考虑不同的解决方案。本文先从比较简单的方案说起。
假设能匹配数据库保存的账号密码,或者能匹配内存中保存的账号密码两者之一,就算认证通过。附加条件:
- 这两种认证方式是通过同一方式提交用户密码;
- 这两种认证方式是一条链式的处理方式。比如先判断第一种方式是否通过认证,不通过则判断第二种。
上述情况,我们考虑使用多个provider去处理。
架构
前面的文章讲过ProviderManager是AuthenticationManager的实现。其架构图如下:

在《Spring Security学习(五)——账号密码的存取》 中,我们说过DaoAuthenticationProvider撬动UserDetailsService和PasswordEncoder的“杠杆”。DaoAuthenticationProvider则是AuthenticationProvider的默认实现。Spring Security通过AuthenticationProvider调用获取UserDetails,然后进行匹配,最后返回UsernamePasswordAuthenticationToken(Authentication接口的具体)。
对于Spring Security的当存在多个AuthenticationProvider的实现时,用户提交的账号密码满足其中任意一个AuthenticationProvider,返回正确的Authentication即可。
自定义AuthenticationProvider
我们做个简单的自定义AuthenticationProvider。自定义MyProvider类:
@Data
public class MyProvider extends AbstractUserDetailsAuthenticationProvider{private UserDetailsServiceImpl userDetailsServiceImpl;private PasswordEncoder passwordEncoder;private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";private volatile String userNotFoundEncodedPassword;@Overrideprotected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {UserDetails loadedUser = userDetailsServiceImpl.loadUserByMemory(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}private void prepareTimingAttackProtection() {if (this.userNotFoundEncodedPassword == null) {this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);}}private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {if (authentication.getCredentials() != null) {String presentedPassword = authentication.getCredentials().toString();this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);}}
}
自定义Provider应该实现AuthenticationProvider接口,并实现authenticate方法。这里我们参考DaoAuthenticationProvider来写,重写retrieveUser方法调用我们自定义的userDetailsServiceImpl的loadUserByMemory方法从内存获取。additionalAuthenticationChecks方法的作用是对提交的密码和系统中保存的密码进行匹配,这里直接参考DaoAuthenticationProvider类的写法。prepareTimingAttackProtection和mitigateAgainstTimingAttack都是用来防御计时攻击的,非认证必要。
在《Spring Security学习(二)——使用数据库保存密码》中我们定义了SysUserServiceImpl,这里进行一些修改:
@Component
public class UserDetailsServiceImpl implements UserDetailsService{@Autowiredprivate SysUserService userService;private static Map<String, SysUserEntity> userMap;static {userMap = new HashMap<String, SysUserEntity>();SysUserEntity sysUser = new SysUserEntity();sysUser.setUsername("test");sysUser.setPassword("test###");userMap.put("test", sysUser);}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<SysUserEntity>();queryWrapper.eq("username", username);queryWrapper.last("limit 1");SysUserEntity user = userService.getOne(queryWrapper);if(user == null) {throw new UsernameNotFoundException("username not found");}return (new LoginUser(user));}public UserDetails loadUserByMemory(String username) throws UsernameNotFoundException {if(StrUtil.isNotEmpty(username)) {SysUserEntity user = userMap.get(username);if(user == null) {throw new UsernameNotFoundException("username not found");}return (new LoginUser(user));}return null;}
}
首先我们增加一个loadUserByUsername方法,用于读取map中保存的测试账号。另外之前当我们查询找不到对象的时候,抛出了RuntimeException,按照Spring Security的设计思想来说是不对的,应该抛出UsernameNotFoundException让上层接住这个异常。抛出RuntimeException会直接终止后面provider的匹配,不符合Spring Security的设计理念。
自定义密码加密器还是使用之前文章的版本MyPasswordEncoder,为原密码后加3个#:
public class MyPasswordEncoder implements PasswordEncoder {@Overridepublic String encode(CharSequence rawPassword) {return rawPassword + "###";}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {if (encodedPassword.equals(rawPassword + "###")) {return true;}return false;}
}
另外由于自定义加密方式,所以数据库密码也要改一下:

最后修改一下WebSecurityConfig的配置,把自定义的MyProvider加进去:
@EnableWebSecurity
public class WebSecurityConfig{@Beanpublic MyPasswordEncoder PasswordEncoder() {return new MyPasswordEncoder();}@Beanpublic SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()).formLogin(Customizer.withDefaults());MyProvider myProvider = new MyProvider();myProvider.setPasswordEncoder(PasswordEncoder());UserDetailsServiceImpl UserDetailsServiceImpl = SpringUtils.getBean(UserDetailsServiceImpl.class);myProvider.setUserDetailsServiceImpl(UserDetailsServiceImpl);http.authenticationProvider(myProvider);DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setPasswordEncoder(PasswordEncoder());daoAuthenticationProvider.setUserDetailsService(UserDetailsServiceImpl);http.authenticationProvider(daoAuthenticationProvider);return http.build();}
}
16行新建MyProvider,17行设置自定义的加密器,18-19行设置UserDetailsServiceImpl。20行加到HttpSecurity中。22行设置DaoAuthenticationProvider到HttpSecurity中。
之后我们同样是访问/hello路径,然后在默认登录页分别尝试使用jake/123,test/test两个账号登陆。会发现通过两种认证方式都可以登陆成功。
小结
本文使用自定义Provider的方式让用户匹配多种认证方式。这个只是最简单的方式。从系列文章的本文章开始,就涉及很多需要了解Spring Security架构设计的情况,读者最好还是阅读源码去理解。否则生搬硬套很难达到实际开发想要达到的目的。本文主要涉及ProviderManager、DaoAuthenticationProvider、AbstractUserDetailsAuthenticationProvider的源码。读者最好阅读一下以了解本文实现的思路。
其实还有个小疑问,WebSecurityConfig似乎我们去掉DaoAuthenticationProvider的配置,一样能达到同样的效果,那我们的配置是不是有点多余了?其实这涉及另一个概念,留到下一篇文章再讲。
相关文章:
Spring Security学习(六)——配置多个Provider(存在两种认证规则)
前言 《Spring Security学习(五)——账号密码的存取》一文已经能满足一般应用的情况。但实际商业应用也会存在如下的情况:用户提交的账号密码,能在本地的保存的账号密码匹配上,或者能在远端服务认证中匹配上ÿ…...
Js如何判断两个数组是否相等?
本文目录 1、通过数组自带方法比较2、通过循环判断3、toString()4、join()5、JSON.stringify() 日常开发,时不时会遇到需要判定2个数组是否相等的情况,需要实现考虑的场景有: 先判断长度,长度不等必然不等元素位置其他情况考虑 1…...
Salesforce顾问如何拿到更高的薪水?
顾问的角色已经在Salesforce生态系统存在了一段时间,随着Salesforce针对职业发展的Trailhead培训模块的发布,该角色的热度又达到了新的浪潮。越来越多人走上了Salesforce顾问这条职业道路。 当然其薪资水平也非常可观,据调查,美国…...
关于React中的状态和属性
在React中,状态(State)和属性(Props)是两个核心概念,用于管理组件的数据和传递信息。下面详细描述它们的区别: 状态(State): 定义: 状态是组件内部…...
【面试题】谈谈MySQL的索引
索引是啥 可以把Mysql的索引看做是一本书的目录,当你需要快速查找某个章节在哪的时候,就可以利用目录,快速的得到某个章节的具体的页码。Mysql的索引就是为了提高查询的速度,但是降低了增删改的操作效率,也提高了空间…...
python工具方法 45 基于ffmpeg以面向对象多线程的方式实现实时推流
1、视频推流 参考基于ffmpeg模拟监控摄像头输出rtsp视频流并opencv播放 实现视频流的推流。 其基本操作就是,安装视频流推流服务器,ffmpeg,准备好要推流的视频。 命令如下所示:ffmpeg -re -stream_loop -1 -i 风景视频素材分享.flv -c copy -f rtsp rtsp://127.0.0.1:554/…...
HarmonyOS Stage模型基本概念讲解
本文 我们来说harmonyos中的一种应用模型 Stage模型 官方提供了两种模型 一种是早期的 FA模型 另一种就是就是 harmonyos 3.1才开始的新增的一种模型 Stage模型 目前来讲 Stage 会成为现在乃至将来 长期推进的一种模型 也就是 无论是 现在的harmonyos 4.0 乃至 之后要发布的 …...
python自动化接口测试
前几天,同组姐妹说想要对接口那些异常值进行测试,能否有自动化测试的方法。仔细想了一下,工具还挺多,大概分析了一下: 1、soapui:可以对接口参数进行异常值参数化,可以加断言,一般我们会加http…...
深度学习????????
深度学习是人工智能领域的一个重要分支,它利用神经网络模拟人类大脑的学习过程,通过大量数据训练模型,使其能够自动提取特征、识别模式、进行分类和预测等任务。近年来,深度学习在多个领域取得了显著的进展,尤其在自然…...
人工智能技术学习专栏文章汇总—帮助你入门深度学习
人工智能大潮已来,stay hungry, stay foolish! 人工智能技术学习类文章汇总,帮助你入门深度学习。 人工智能学习与实训笔记(一):零基础理解神经网络-CSDN博客 人工智能学习与实训笔记(二)&am…...
线性代数:向量空间
目录 向量空间 Ax 0 的解空间S Ax b 的全体解向量所构成集合不是向量空间 基、维数、子空间 自然基与坐标 例1 例2 向量空间 Ax 0 的解空间S Ax b 的全体解向量所构成集合不是向量空间 基、维数、子空间 自然基与坐标 例1 例2...
Pormise---如何解决javascript中回调的信任问题?【详解】
如果阅读有疑问的话,欢迎评论或私信!! 本人会很热心的阐述自己的想法!谢谢!!! 文章目录 回调中的信任问题回调给我们带来的烦恼?调用过早调用过晚调用的次数太少或太多调用回调时未能…...
如何选择最适合的图纸加密软件?用户体验及性价比
安秉网盾图纸加密软件是一款功能强大的图纸加密工具,具有以下特点和优势: 全盘加密:安秉网盾采用先进的加密算法,能对文件、文件夹、磁盘等数据进行全面加密,确保数据在存储和传输过程中的安全性。 监控与审计&#…...
一分钟学会MobaXterm当Linux客户端使用
一、介绍 MobaXterm是一款功能强大的远程计算机管理工具,它集成了各种网络工具和远程连接协议,可以帮助用户在Windows系统上轻松管理远程计算机。MobaXterm支持SSH、Telnet、RDP、VNC等多种远程连接协议,同时还集成了X11服务器,可…...
2024-02-21 算法: 测试链表是否有环
点击 <C 语言编程核心突破> 快速C语言入门 算法: 测试链表是否有环 前言一、双指针 ( 快慢指针 )二、代码总结 前言 要解决问题: 一道简单的算法题, 测试链表是否含有环. 想到的思路: 哈希表, 将链表指针强制转换为整型, 利用求余法建立哈希函数. 太复杂, 内存效率不高…...
http协议工具:apache详解
目录 一、常见的http服务程序 1、 Apache HTTP Server 介绍 1.1 apache 概念 1.2 apache 功能 1.3 apache 特性 2、MPM(multi-processing module)工作模式 2.1 prefork 2.2 worker 2.3 event 二、Apache HTTP Server安装和相关文件 1、安装方…...
我的NPI项目之Android Camera (二) -- 核心部件之 Camera Sensor
说到Camera模组,我们比较关心的是用的什么样的sensor? sensor的分辨率多少,sensor的像素多大,sensor是哪家生产的等等一些问题。今天,我们就穿越时间,将sensor的历史扒一扒。 Wikipedia先看一下࿱…...
【四】3D Object Model之测量Features——get_object_model_3d_params()算子
😊😊😊欢迎来到本博客😊😊😊 🌟🌟🌟 Halcon算子太多,学习查找都没有系统的学习查找路径,本专栏主要分享Halcon各类算子含义及用法,有…...
C++学习Day09之系统标准异常
目录 一、程序及输出1.1 系统标准异常示例1.2 标准异常表格 二、分析与总结 一、程序及输出 1.1 系统标准异常示例 #include<iostream> using namespace std; #include <stdexcept> // std 标准 except 异常class Person { public:Person(int age){if (age <…...
企业计算机服务器中了crypt勒索病毒怎么办,crypt勒索病毒解密数据恢复
计算机服务器设备为企业的生产运营提供了极大便利,企业的重要核心数据大多都存储在计算机服务器中,保护企业计算机服务器免遭勒索病毒攻击,是一项艰巨的工作任务。但即便很多企业都做好的了安全运维工作,依旧免不了被勒索病毒攻击…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...
