共计 5412 个字符,预计需要花费 14 分钟才能阅读完成。
松哥最近在钻研 Spring Security 源码,发现了很多好玩的代码,抽空写几篇文章和小伙伴们分享一下。
很多人吐槽 Spring Security 比 Shiro 重量级,这个重量级不是凭空来的,分量有分量的益处,就是它提供了更为弱小的防护性能。
比方松哥最近看到的一段代码:
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {prepareTimingAttackProtection();
try {UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(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);
}
}
这段代码位于 DaoAuthenticationProvider 类中,为了不便大家了解,我来简略说下这段代码的上下文环境。
当用户提交用户名明码登录之后,Spring Security 须要依据用户提交的用户名去数据库中查问用户,这块如果大家不相熟,能够参考松哥之前的文章:
- Spring Security 如何将用户数据存入数据库?
- Spring Security+Spring Data Jpa 强强联手,平安治理只有更简略!
查到用户对象之后,再去比对从数据库中查到的用户明码和用户提交的明码之间的差别。具体的比对工作,能够参考 Spring Boot 中明码加密的两种姿态!一文。
而下面这段代码就是 Spring Security 依据用户登录时传入的用户名去数据库中查问用户,并将查到的用户返回。办法中还有一个 authentication 参数,这个参数里边保留了用户登录时传入的用户名 / 明码信息。
那么这段代码有什么神奇之处呢?
咱们来一行一行剖析。
源码梳理
1
首先办法一进来调用了 prepareTimingAttackProtection 办法,从办法名字上能够看出,这个是为计时攻打的进攻做筹备,那么什么又是计时攻打呢?别急,松哥一会来解释。咱们先来吧流程走完。prepareTimingAttackProtection 办法的执行很简略,如下:
private void prepareTimingAttackProtection() {if (this.userNotFoundEncodedPassword == null) {this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
该办法就是将常量 USER_NOT_FOUND_PASSWORD 应用 passwordEncoder 编码之后(如果不理解 passwordEncoder,能够参考 Spring Boot 中明码加密的两种姿态!一文),将编码后果赋值给 userNotFoundEncodedPassword 变量。
2
接下来调用 loadUserByUsername 办法,依据登录用户传入的用户名去数据库中查问用户,如果查到了,就将查到的对象返回。
3
如果查问过程中抛出 UsernameNotFoundException 异样,按理说间接抛出异样,接下来的明码比对也不必做了,因为依据用户名都没查到用户,这次登录必定是失败的,没有必要进行明码比对操作!
然而大家留神,在抛出异样之前调用了 mitigateAgainstTimingAttack 办法。这个办法从名字上来看,有缓解计时攻打的意思。
咱们来看下该办法的执行流程:
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {if (authentication.getCredentials() != null) {String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
能够看到,这里首先获取到登录用户传入的明码即 presentedPassword,而后调用 passwordEncoder.matches 办法进行明码比对操作,原本该办法的第二个参数是数据库查问进去的用户明码,当初数据库中没有查到用户,所以第二个参数用 userNotFoundEncodedPassword 代替了,userNotFoundEncodedPassword 就是咱们一开始调用 prepareTimingAttackProtection 办法时赋值的变量。这个明码比对,从一开始就注定了必定会失败,那为什么还要比对呢?
计时攻打
这就引入了咱们明天的主题 – 计时攻打。
计时攻打是旁路攻打的一种,在密码学中,旁道攻打又称侧信道攻打、边信道攻打(Side-channel attack)。
这种攻击方式并非利用加密算法的实践弱点,也不是暴力破解,而是从明码零碎的物理实现中获取的信息。例如:工夫信息、功率耗费、电磁泄露等额定的信息源,这些信息可被用于对系统的进一步破解。
旁路攻打有多种不同的分类:
- 缓存攻打(Cache Side-Channel Attacks),通过获取对缓存的拜访权而获取缓存内的一些敏感信息,例如攻击者获取云端主机物理主机的拜访权而获取存储器的拜访权。
- 计时攻打(Timing attack),通过设施运算的用时来推断出所应用的运算操作,或者通过比照运算的工夫推定数据位于哪个存储设备,或者利用通信的时间差进行数据窃取。
- 基于功耗监控的旁路攻打,同一设施不同的硬件电路单元的运作功耗也是不一样的,因而一个程序运行时的功耗会随着程序应用哪一种硬件电路单元而变动,据此推断出数据输入位于哪一个硬件单元,进而窃取数据。
- 电磁攻打(Electromagnetic attack),设施运算时会透露电磁辐射,通过切当剖析的话可解析出这些透露的电磁辐射中蕴含的信息(比方文本、声音、图像等),这种攻击方式除了用于密码学攻打以外也被用于非密码学攻打等窃听行为,如 TEMPEST 攻打。
- 声学明码剖析(Acoustic cryptanalysis),通过捕获设施在运算时透露的声学信号捉取信息(与功率剖析相似)。
- 差异谬误剖析,隐密数据在程序运行产生谬误并输入错误信息时被发现。
- 数据残留(Data remanence),可使理当被删除的敏感数据被读取进去(例如冷启动攻打)。
- 软件初始化谬误攻打,现时较为少见,行锤攻打(Row hammer)是该类攻击方式的一个实例,在这种攻打实现中,被禁止拜访的存储器地位旁边的存储器空间如果被频繁拜访将会有状态保留失落的危险。
- 光学形式,即隐密数据被一些视觉光学仪器(如高清晰度相机、高清晰度摄影机等设施)捕获。
所有的攻打类型都利用了加密 / 解密零碎在进行加密 / 解密操作时算法逻辑没有被发现缺点,然而通过物理效应提供了有用的额定信息(这也是称为“旁路”的原因),而这些物理信息往往蕴含了密钥、明码、密文等隐密数据。
而下面 Spring Security 中的那段代码就是为了避免计时攻打。
具体是怎么做的呢?假如 Spring Security 从数据库中没有查到用户信息就间接抛出异样了,没有去执行 mitigateAgainstTimingAttack 办法,那么黑客通过大量的测试,再通过统计分析,就会发现有一些登录验证耗时显著少于其余登录,进而推断出登录验证工夫较短的都是不存在的用户,而登录耗时较长的是数据库中存在的用户。
当初 Spring Security 中,通过执行 mitigateAgainstTimingAttack 办法,无论用户存在或者不存在,登录校验的耗时不会有显著差异,这样就防止了计时攻打。
可能有小伙伴会说,passwordEncoder.matches 办法执行能消耗多少工夫呀?这要看你怎么计时了,工夫单位越小,差别就越显著:毫秒(ms)、微秒(µs)、奈秒(ns)、皮秒(ps)、飛秒(fs)、阿秒(as)、仄秒(zs)。
另外,Spring Security 为了平安,passwordEncoder 中引入了一个概念叫做自适应单向函数,这种函数成心执行的很慢并且耗费大量系统资源,所以十分有必要进行计时攻打进攻。
对于自适应单向函数,这是另外一个故事了,松哥抽空再和小伙伴们聊~
好啦,明天就先和小伙伴们聊这么多,小伙伴们决定有播种的话,记得点个在看激励下松哥哦~
人不知; 鬼不觉,Spring Security 系列曾经连载了 49 篇啦,感兴趣的小伙伴也能够看看本系列其余文章哦:
- 挖一个大坑,Spring Security 开搞!
- 松哥手把手带你入门 Spring Security,别再问明码怎么解密了
- 手把手教你定制 Spring Security 中的表单登录
- Spring Security 做前后端拆散,咱就别做页面跳转了!通通 JSON 交互
- Spring Security 中的受权操作原来这么简略
- Spring Security 如何将用户数据存入数据库?
- Spring Security+Spring Data Jpa 强强联手,平安治理只有更简略!
- Spring Boot + Spring Security 实现主动登录性能
- Spring Boot 主动登录,平安危险要怎么管制?
- 在微服务项目中,Spring Security 比 Shiro 强在哪?
- SpringSecurity 自定义认证逻辑的两种形式 (高级玩法)
- Spring Security 中如何疾速查看登录用户 IP 地址等信息?
- Spring Security 主动踢掉前一个登录用户,一个配置搞定!
- Spring Boot + Vue 前后端拆散我的项目,如何踢掉已登录用户?
- Spring Security 自带防火墙!你都不晓得本人的零碎有多平安!
- 什么是会话固定攻打?Spring Boot 中要如何进攻会话固定攻打?
- 集群化部署,Spring Security 要如何解决 session 共享?
- 松哥手把手教你在 SpringBoot 中进攻 CSRF 攻打!so easy!
- 要学就学透彻!Spring Security 中 CSRF 进攻源码解析
- Spring Boot 中明码加密的两种姿态!
- Spring Security 要怎么学?为什么肯定要成体系的学习?
- Spring Security 两种资源放行策略,千万别用错了!
- 松哥手把手教你入门 Spring Boot + CAS 单点登录
- Spring Boot 实现单点登录的第三种计划!
- Spring Boot+CAS 单点登录,如何对接数据库?
- Spring Boot+CAS 默认登录页面太丑了,怎么办?
- 用 Swagger 测试接口,怎么在申请头中携带 Token?
- Spring Boot 中三种跨域场景总结
- Spring Boot 中如何实现 HTTP 认证?
- Spring Security 中的四种权限管制形式
- Spring Security 多种加密计划共存,老破旧零碎整合利器!
- 神奇!本人 new 进去的对象一样也能够被 Spring 容器治理!
- Spring Security 配置中的 and 到底该怎么了解?
- 一文搞定 Spring Security 异样解决机制!
- 写了这么多年代码,这样的登录形式还是头一回见!
- Spring Security 居然能够同时存在多个过滤器链?
- Spring Security 能够同时对接多个用户表?
- 在 Spring Security 中,我就想从子线程获取用户登录信息,怎么办?
- 深刻了解 FilterChainProxy【源码篇】
- 深刻了解 SecurityConfigurer【源码篇】
- 深刻了解 HttpSecurity【源码篇】
- 深刻了解 AuthenticationManagerBuilder【源码篇】
- 花式玩 Spring Security,这样的用户定义形式你可能没见过!
- 深刻了解 WebSecurityConfigurerAdapter【源码篇】
- 盘点 Spring Security 框架中的八大经典设计模式
- Spring Security 初始化流程梳理
- 为什么你应用的 Spring Security OAuth 过期了?松哥来和大家捋一捋!
- 一个诡异的登录问题