深入研究防止重复请求的方式

对于防止重复请求网上有很多种方案,但是提方案的时候碰到一个巨大的坑,是在方法二中出现的。

在最后我会给一个测试方法,大家自己测试。

方法一:

$("#submit").attr('disabled','true');
$("#submit").val("正在提交,请稍等");

原理是将按钮禁用,防止下次点击

缺陷:

只是从外观商防止重复提交而已,除了看上去好一点实际上并没有什么卵用。(浏览器也有卡的时候,用户存在多次点击的情况)

 

方法二:

token检测法。

大概原理是提交之前生成一个token,把这个token存入到session或者cookie当中,表单或者ajax提交的时候把这个token一起带上,后台收到请求的时候检查这个token和cookie或者session中设置的是否一致。

 

小坑一:将token存储在cookie中,用户假如没有刷新页面的情况下,点击多次,发送了多次请求,每次发送的cookie和token一样,所以这种方法并没有什么卵用,存在cookie中的开发者脑子肯定被烧过。

小坑二:收到请求的时候注意一下,页面中的ajax请求有没有可能会导致重新刷新token,这个比较显眼,也算小坑

大坑在这里:将token存储在session中,看上去没有任何问题,但是session必须使用php的原生session,也就是说不要去重写session的save_handler。

重写的session一般都没有阻塞,假设将session存储在数据库或者redis里面,当2个请求同时到达(网络问题),2个请求都会验证通过的,下面我会上传一段测试代码,大家回去自己测试


<?php $url = 'http://localhost/test3.php';//测试的地址 $token = '4a58c440aece3502157a5327d45df97f';//生成的token $curl1 = curl_init($url); curl_setopt_array($curl1,array( CURLOPT_HEADER => 0,
CURLOPT_HTTPHEADER => array(
'X-CSRF-TOKEN: '.$token,
'Cookie: PHPSESSID=j5744goo7rtu62k9jplu35rtb1'
),
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => array(
'test' => '2341',
'token'=>$token
),
CURLOPT_RETURNTRANSFER => 1,

));

$curl2 = curl_init($url);
curl_setopt_array($curl2,array(
CURLOPT_HEADER => 0,
CURLOPT_HTTPHEADER => array(
'X-CSRF-TOKEN: '.$token,
'Cookie: PHPSESSID=j5744goo7rtu62k9jplu35rtb1'
),
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => array(
'test' => '1234',
'token'=>$token
),
CURLOPT_RETURNTRANSFER => 1,

));

$mh = curl_multi_init();

// 增加2个句柄
curl_multi_add_handle($mh,$curl1);
curl_multi_add_handle($mh,$curl2);

$running=null;
// 执行批处理句柄
do {
//usleep(10000);
curl_multi_exec($mh,$running);
} while ($running > 0);

// 关闭全部句柄
curl_multi_remove_handle($mh, $curl1);
curl_multi_remove_handle($mh, $curl2);
curl_multi_close($mh);

$response_1 = curl_multi_getcontent($curl1);
//var_dump($response_1);
$response_2 = curl_multi_getcontent($curl2);
var_dump($response_1,$response_2);
?>

以上代码同时发起2次请求,测试结果是当session存储在mysql当中的时候2次请求同时绕过了验证,当session存储在redis里面的时候,发起5次请求,5次中有2-4次验证通过(redis的高性能),所以session并不是验证重复请求的关键,那么关键是什么

 

当php使用session的时候执行的代码是session_start,假如使用php原生的session的时候,session_start会打开session文件,并且加锁,加锁才是最关键的地方,当2个请求同时到达的时候,其中一个请求会要求对session文件加锁,而另一个请求会在这里等待,等待状态的请求一直处于阻塞状态,并没有进入验证逻辑,当第一个请求执行完所有代码后php会自动执行session_write_close函数,而这个函数会将上面的锁释放,这个时候第二个请求才会开始验证是否合法的,也就是说通过session的文件锁实现了一个队列的等待功能。因此,将session存储在第三方共享存储的基本上都没有这个锁机制,或者当读取完数据后就自动释放掉了,并没有办法实现重复提交验证的问题

发表评论

电子邮件地址不会被公开。