松哥最近研究了SpringSecurity的源码,发现了很多有意思的代码。我抽空写了几篇文章与朋友们分享。很多人抱怨SpringSecurity比Shiro重。这个重量不是凭空而来的。权重的优势在于它提供了更强大的保护功能。比如松哥最近看到的一段代码:protectedfinalUserDetailsretrieveUser(Stringusername,UsernamePasswordAuthenticationTokenauthentication)throwsAuthenticationException{prepareTimingAttackProtection();try{UserDetailsloadedUser=this.getUserDetailsS??ervice().loadUserByUsername(username);if(loadedUser==null){thrownewInternalAuthenticationServiceException("UserDetailsS??ervicereturnednull,whichisaninterfacecontractviolation");}returnloadedUser;}catch(UsernameNotFoundExceptionex){mitigateAgainstTimingAttack(authentication);throwex;}catch(InternalAuthenticationServiceExceptionex){throwex;}catch(Exceptionex){thrownewInternalAuthenticationServiceException(ex).getMessage);}(本段)位于DaoAuthenticationProvider类中,为了方便大家理解,我简单说一下这段代码的上下文。用户提交用户名和密码登录后,SpringSecurity需要根据用户提交的用户名在数据库中查询用户。找到用户对象后,比较从数据库中查到的用户密码和用户提交的密码的区别。上面代码是SpringSecurity根据用户登录时传入的用户名在数据库中查询用户,返回找到的用户。方法中还有一个认证参数,里面存放的是用户登录时传入的用户名/密码信息。那么这段代码有什么神奇之处呢?我们一行一行来分析。源码梳理1、首先方法1调用prepareTimingAttackProtection方法。从方法名可以看出,这是为防御定时攻击做的准备。那么什么是定时攻击呢?别着急,宋大哥稍后会解释。让我们先走,完成这个过程。prepareTimingAttackProtection方法的实现很简单,如下:编码后(Encoder,如果不知道passwordEncoder,可以参考SpringBoot中密码加密的两种姿势!一文),将编码结果赋值给变量userNotFoundEncodedPassword。2、接下来调用loadUserByUsername方法根据登录用户传入的用户名查询数据库中的用户。如果找到,将返回找到的对象。3.如果在查询过程中抛出UsernameNotFoundException,按理说直接抛出异常,接下来的密码比对就不用做了,因为根据用户名没有找到用户,登录肯定有这次失败了,没必要做了。密码比对操作!但请注意,在抛出异常之前会调用mitigateAgainstTimingAttack方法。从名字上看,该方法具有缓解定时攻击的意思。我们看一下这个方法的执行流程:.userdNotPassFound);}}可以看出,这里首先获取了登录用户传入的密码presentedPassword,然后调用passwordEncoder.matches方法进行密码比对操作。本来这个方法的第二个参数是从数据库中查询到的用户密码。现在数据库中没有找到用户,所以第二个参数换成userNotFoundEncodedPassword,也就是我们第一次调用prepareTimingAttackProtection方法时赋值的变量。这种密码比对从一开始就注定失败,为什么还要比对呢?定时攻击这就引出了我们今天的话题——定时攻击。定时攻击是一种边信道攻击。在密码学中,侧信道攻击也称为边信道攻击或侧信道攻击。这种攻击方式并没有利用加密算法的理论弱点,也不是暴力破解,而是从密码系统的物理实现中获取的信息。例如:时间信息、功耗、电磁泄漏等附加信息源,可用于进一步破解系统。侧信道攻击有很多不同的分类:缓存侧信道攻击(CacheSide-ChannelAttacks),通过获取缓存的访问权限来获取缓存中的一些敏感信息,例如,攻击者获取物理主机的访问权限云主机获取存储访问权限。定时攻击(Timingattack),通过设备的计算时间推断所使用的计算操作,或通过比较计算时间推断出数据位于哪个存储设备,或利用通信时间差窃取数据。基于功耗监控的侧信道攻击,同一设备的不同硬件电路单元的运行功耗也是不同的,所以程序运行时的功耗会随着程序使用的硬件电路单元不同而不同.据此,可以推断出输出数据位于哪个硬件单元,进而窃取数据。电磁攻击(Electromagneticattack),设备在运行过程中会泄漏电磁辐射。经过适当的分析,可以解析出泄漏的电磁辐射中所包含的信息(如文字、声音、图像等)。这种攻击方式不仅用于密码学攻击之外,还用于非密码学攻击等窃听行为,如TEMPEST攻击。声学密码分析通过捕获设备在运行期间泄漏的声学信号来捕获信息(类似于功率分析)。差分错误分析,在程序运行出错时发现隐藏数据,并输出错误信息。数据残留,允许读出应该删除的敏感数据(例如冷启动攻击)。软件初始化错误攻击目前比较少见。行锤攻击是此类攻击的一个例子。在这种攻击实现中,如果被禁止的内存位置旁边的内存空间被频繁访问,就会有stateReserve丢失的风险。光学方式,即通过一些视觉光学仪器(如高清摄像机、高清摄像机等)获取秘密数据。所有类型的攻击都是利用加解密系统的算法逻辑在加解密操作过程中没有发现缺陷,而是通过物理效应提供有用的附加信息(这就是为什么叫“旁路”)),而这些物理信息通常包括密钥、密码和密文等秘密数据。SpringSecurity中的上述代码是为了防止定时攻击。你怎么做呢?假设SpringSecurity没有从数据库中查找用户信息,直接抛出异常,也没有执行mitigateAgainstTimingAttack方法,那么黑客经过大量的测试和统计分析,会发现一些登录验证。明显比其他登录耗时少,可以推断登录验证时间较短的用户是不存在的用户,而登录耗时较长的用户是数据库中存在的用户。现在在SpringSecurity中,通过执行mitigateAgainstTimingAttack方法,无论用户是否存在,登录验证的耗时都不会有明显差异,从而避免了定时攻击。可能有朋友会说,执行passwordEncoder.matches方法需要多少时间?这取决于你如何计时。时间单位越小,区别越明显:毫秒(ms)、微秒(μs)、纳秒(ns)、皮秒(ps)、飞秒(fs)、阿秒(as)、zeptosecond(zs)。另外,为了安全起见,SpringSecurity在passwordEncoder中引入了一个概念,叫做自适应单向函数。该函数故意执行缓慢,消耗大量系统资源,因此防御定时攻击是非常必要的。关于自适应单向函数,这是另外一回事了。宋哥会抽空和小伙伴们聊聊的~好了,今天就和小伙伴们聊这么多。小伙伴们如果决定有所收获,记得点这里观看鼓励宋哥~本文转载自微信公众号《江南的一场小雨》,可以通过以下二维码关注.转载本文请联系江南一点鱼公众号。
