松哥最近在研究 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 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 方法时赋值的变量 。这个密码比对,从一开始就注定了肯定会失败,那为什么还要比对呢?计时攻击这就引入了我们今天的主题--计时攻击 。
推荐阅读
- 红茶减肥周瘦八斤,不只是减肥哦
- 网站优化也是从量变到质变的过程
- 霍山黄芽什么时间采,霍山黄芽怎么泡
- 喝苦菜茶有什么好处,喝玫瑰花有什么好处
- 如何使用 WebGL 进行实时视频处理
- 志愿军跨过鸭绿江入朝作战和什么事件有关 志愿军渡过鸭绿江
- 卡廷惨案到底是谁干的 卡廷惨案真相大揭秘
- 外强中干的人成语怎么说 比喻外强中干的人是什么
- 秦相范雎的结局 秦相范雎的结局
- 红茶是熟茶吗,不同山头原料发酵的熟茶