上联:这个需求很简单
下联:怎么实现我不管
横批:今晚上线
Part 1:暴力破解早上开完站会,小李领了张新卡,要对登录功能做升级改造,在原来只支持用户名密码登录模式的基础上,新增手机号和短信验证码登录。
业务分析师薇薇早就准备好了故事卡,并且也考虑到这个功能的特殊性,除了平常的业务性验收标准外,还专门添加了一些和安全有关的条目。这张故事卡看上去是这样的:
故事卡-274
作为用户,我可以通过手机号和短信验证码登录,以便于我更方便的登录。
安全验收标准:
短信验证码有效期5分钟
验证码为4位纯数字
每个手机号60秒内只能发送一次短信验证码
小李看到故事卡中提到,验证码长度只有4位而且还是纯数字,隐约觉得强度有些不够,担心万一黑客来个多线程并发请求,或者拿一个集群来暴力登录,有可能会赶在有效期内破解出合法的验证码。
小李把自己的担心讲给了业务分析师薇薇,并且建议把验证码长度增加到6位,或者在保持4位长度的情况下,改为数字和字母的组合,目的是增加验证码复杂性,提高暴力破解的门槛。
薇薇听了这两种选择后直摇头,说道:“我理解你的担心,可是业务部门那边的需求很明确,就是为了优化用户登录体验,所以才决定做手机号和验证码登录,如果把验证码弄的这么复杂,那用户体验也好不到哪里去,不符合这个故事卡的初衷啊。”
“对于用户而言,4位数字验证码确实好记好填,可是对于黑客而言,就能很容易的完成暴力枚举,理论上最多1万次请求就能遍历完所有的验证码,更何况黑客没那么倒霉,要尝试到第1万次才猜对……”,小李说道。
为了满足用户体验而在安全性上做出妥协,这种事情小李觉得自己无法说服自己,正准备掏出纸和笔跟薇薇做详细解释黑客攻击手段的时候,团队技术负责人老罗听见了他们两的讨论,慢慢脱下帽子,摸了摸正在朝着“地中海”模式演进的乌黑的秀发,说道:“那啥,服务器在验证登录请求的时候,不管验证码匹配还是不匹配,存在Redis里的验证码只要被取出来就立即作废,根本不给黑客暴力破解的机会。”
小李的团队已经搭建好了Redis,用来存储登录过程中发给用户的短信验证码,是一个手机号和验证码的键值对。
“对啊”,小李感觉眼前一亮,说道,“服务器在比对请求中的验证码和Redis中保存的这个用户手机号所对应的验证码的时候,如果发现不匹配,那依然还是直接把Redis中的这个验证码作废。这样黑客发第二次登录请求的时候,会因为Redis中找不到对应的记录而登录失败。这样既避免了暴力枚举攻击,同时也不再需要增加验证码的强度,导致用户体验的下降了。”
小李建议把刚才的讨论结果写到故事卡里,而薇薇提议能否不要立即作废:“万一用户输入验证码的时候手滑输错了,岂不是要等几十秒的时间再重新发第二个验证码?”
“可以做到验证码3次输入错误后就作废吗?”薇薇问到。
“可以的,这个不难”小李坚定的回答到。
“好,那我们加一条安全验收标准吧”,薇薇边说边修改了故事卡,新增加了一条:
保存于服务器端的验证码,至多可被使用3次(无论和请求中的验证码是否匹配),随后立即作废,以防止暴力攻击
“对了小李”,老罗喝了口咖啡,最近连续的加班让老罗感觉很疲惫,只能靠喝咖啡强打精神。“60秒内只能发1次短信那条,别忘了前后端都要做检查。”
“知道知道,前端做不做都无所谓,关键是在后端要做限制。”小李连连点头。
“好,那就这么做,去忙吧”。老罗转身坐下,正准备继续刚才被打断的工作,此时一个念头快速在脑海里一闪而过,老罗在电脑上打开短信登录的这张故事卡,从头到尾又看了一次,最后目光停留在“短信验证码有效期5分钟”这条验收标准那里。
“短信每60秒发一次”,老罗心想:“但有效期是5分钟,那第61秒的时候假如又请求发送一次验证码,这时第一次发送的验证码还没过期,服务器端该怎么处理这个请求会比较稳妥呢?