该问题源于一次生产环境的小bug,老板创建了几个账号,然后分给地区的城市经理使用,他们却发现有的人密码登陆不了,还好问题不严重,只影响了部分用户登录,并且可以快速修复
-
背景
系统设计之初,是使用 用户名 + 密码 登录(phone + password),所以最开始的密码都是使用 phone 做盐,然后和password一起用md5加密;
后来来了一个修改手机号的需求,也就是这个 phone 需要被修改,但是修改了之后,密码的盐变了,所以修改密码之后,还需要用户重置密码才能登陆,这就显得很繁琐;
我就想到一个解决方案,用一个不变的属性来作为盐,刚开始本来想用ID,这个绝对是唯一的,可是我们系统里面,个人用户升级为企业用户的时候,ID是会发生变化的(其实我觉得就算升级,ID也不应该发生变化,可是这个是我们老大设计的,我也不好去改),所以用ID也不稳当,就想到一个用户注册时间 ctime,这个肯定是不会变化的,于是最终用这个作为 盐
-
问题现象
新用户注册完成之后,系统会给一个默认密码,但是用户使用默认密码登录,却报密码错误
-
原因分析
由于我们最开始的这个ctime字段,和utime一样,类型为timestamp,但是length默认为0,也就是表示只精确到秒(s),所以在java 代码里面运行的时候,我是用的这个ctime.toString()作为盐加密的,这个取值规律只取到秒(s),后面的毫秒(ms)忽略,比如时间为56.888(s),那么后面的888(ms)是会被忽略掉,直接按照56(s)计算;
可是我们保存到数据库的时候,因为只能精确到秒(s),就默认采用了四舍五入的机制,比如456ms,就会当做0s;678ms就会当中1s,此时数据的精度已经丢失;
然后在登陆的时候,我们从数据库取出一个精度已经丢失的ctime来当作盐值加密密码,和原来保存的密码去做比较,如果当时插入的时候,毫秒数(ms)低于500,那么密码是能够校验通过的;如果大于500,保存的密码就比实际密码要大,密码校验就会失败
-
解决方案
将数据库的创建时间(ctime)改为length=3,这样密码保存就可以精确到毫秒(ms),所以毫秒现在就不会丢失,密码校验时用的ctime就可以和创建是的ctime保持一致,从而密码校验成功
可以看到,修改之前的数据,毫秒全部都已丢失了
网友评论