随着互联网的高速发展,WEB2.0网站的业务越来越庞大,一些token验证在许多场景下都必不可少,比如说交易订单的防止多次提交,重要的敏感操作防止CSRF(跨站请求伪造)攻击,以及短信验证码,找回密码验证码,注册登录图形的生成和验证。
token的特点主要有如下几个:
唯一性
时效性
不可预测
很多大型业务中,比如说BAT的找回密码流程中,对于发给用户的找回密码链接邮件需要同时提交用户输入的vcode验证码和vcode_md5也就是该校验码对应的token。很多人认为这时需要一个缓存中间件比如说Redis或者Memcache来存储校验码对应的需要重置密码的用户Uid。其实大可不必如此。
我们可以在服务器端使用rand()生成随机数,然后将生成的随机数加上指定的salt做md5处理,比如说
function vcode($uid) {
$output['vcode_timestamp'] = time();
$output['vcode'] = $uid;
$output['vcode_md5'] = md5("changwei"+vcode_timestamp+$uid);
return $output;
}
生成的时候将vcode和vcode_md5一起通过邮件发回给客户。
那么验证的时候该怎么验证呢?很好办,把指定的盐值加上校验码以及其他附带信息(比如说用户UID)进行md5运算,和客户端提交的那个事先发给客户端的vcode_md5进行比较,如果一致那么就通过。
那么我们来看看这是否具有token的几个特点呢?首先,唯一性,可以通过uid(这个一般是用户表的递增主键,不会重复),并且将当前时间戳作为随机数种子,基本上可以认为是高度随机的数字了,加上md5加盐运算之后客户有视为高度近似的唯一性了。不可预测性呢?由于指定的salt是存储在服务端,只要这个salt足够复杂而且没有被泄漏,那么这个vcode_md5肯定是无法被客户端预测的,也保证了这个方案的安全性。
最后就是时效性了,很多人总在纠结,如果没有Redis或Memcache,那么如何去存储他的时效性呢?通过cookie吗?不行,cookie是在客户端的,那么意味着是用户可控的,不安全。用session呢?标题写了是低成本解决方案,session存储在服务端,在高并发请求数量庞大的大型网站中,每时每刻都有成千上万的校验码生成,如果都存在session对于服务器压力(尤其是IO)是十分大的。那么还有什么办法呢?我们想想,既然md5是消息摘要算法,那么他就可以用来验证数据在传输中途是否经过了篡改,那么思路就来了,我们在发回给用户的表单中再加一个隐藏域,名字为vcode_timestamp,后端算法再改一改,这次提供完整的验证算法源代码。
function vcode($uid) {
$output['vcode_timestamp'] = time();
$output['vcode'] = $uid;
$output['vcode_md5'] = md5("changwei"+vcode_timestamp+$uid);
return $output;
}
function verify() {
if(md5("changwei"+$_POST['vcode_timestamp']+$_POST['uid'])===$_POST['vcode_md5']) {
if(time()<intval($_POST['vcode_timestamp'])+60*60){ // 60*60表示时效性为一个小时
return true;
}
return false;
}
return false;
}
这样一来时效性也可以验证了,全过程没有涉及到任何服务器端的存储引擎,可以说是把成本降到了最低。而且md5本身也是目前效率非常高的hash算法,对于cpu的消耗也不是很大。
目前我的网站lol.changwei.me
中取消监控链接便用的是该方案,经过上线一个月的测试,基本上没有出现什么BUG。