700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > PHP防止表单重复提交

PHP防止表单重复提交

时间:2018-09-29 19:02:17

相关推荐

PHP防止表单重复提交

日常开发中,表单的提交是无法避免的,而我们必须熟知的一点是“在做后退或刷新操作时,post会重新提交请求是有害的,而get虽然会重新获取数据但却是无害的”。所以我们要禁止用户重复提交表单。

首先,我们要知道在什么情况下表单会重复提交

点击提交按钮两次。点击刷新按钮。回退,然后重复操作。进行一些恶意操作。

那么,如何防止表单重复提交呢?

其实很简单,根据数据流向的过程,可以从三个层面进行控制:

1、前端层面

用户点击按钮触发submit时,前端js控制提交按钮的状态,将按钮的disabled属性为true,防止重复点击。

//监听submit事件,触发后将提交按钮设置为不可用$('form').submit(function() {$('button[type=submit]').attr('disabled', true);});

基本上禁用按钮后,前端不用管了,因为数据如果通不过laravel的验证或发生异常,在laravel中的做法是back()->withInput()->withErrors();这时页面会刷新,按钮会自行恢复状态。

如果是ajax方式提交,直接在function里发送ajax请求前禁用就行了,然后根据请求的结果来恢复按钮的状态或跳转页面就可以了。

2、服务端层面

思路::

在显示表单页面时,服务端生成一个随机字符串并以该字符串为key保存在session中并将其回显在表单一个隐藏的input中,当提交表单时,服务端根据这个隐藏input的值(即session中的key)去session中取值,如果该key存在于session中表示正常提交,并立即从session中删除该key,若发生重复提交,session中的这个key已经被删除了,就可以给前端相应的提示“表单重复提交”。

缺点:

刷新界面,会导session中存放了多个key,数据冗余且存在漏洞,因为存在多个key即意味着同一时间可以使用不同key来提交同一份数据;

【补充】laravel中可以通过flash方法来存储只在下个请求有效的session数据,即在下一请求之后,该数据会被自动从session中清除,这样确实能解决刷新界面后session中保存多个key的问题,但会带来一个新的问题,列举一个场景加以说明:假如某用户正在写评论,写到一半被旁边推荐的一篇文章吸引,就先去看文章了,等看完回来继续写完评论提交,会发生什么事?会被当做表单重复提交处理,因为查看文章时,已经将flash方式保存的session清空了。

不够简洁,要知道这里解决的问题是要防止表单重复提交,完全没有必要生成一个动态的类似token东西,针对某一类表单提交(如注册)将存储在session中的key固定就好了,这样就可以省去form中那个隐藏的input了。

优化后的思路:

针对不同类型的表单(这里定义登陆、注册为不同类型的表单)服务端维护多个不同的key(比如登陆表单在session中对应的key固定为‘login’,注册表单的key固定为’register’),在显示表单页面时将key保存进session(对应的value可以存1,也可以存当前时间,存当前时间的话,你可以根据在提交表彰时根据时间间隔来作进一步的控制),表单提交时将其删除,若出现重复提交,session中不存在这个key,你就可以提示用户“不要重复提交”了。

具体实现:

1、在controller中显示注册界面的方法里保存session

public function showRegistrationForm(Request $request){ $request->session()->put('register',time());return view('auth.register');}

2、在处理表单提交方法中判断是否重复提交

public function register(Request $request){if($this->request->session()->has(‘register’)){//存在则表示是首次提交,清空session中的'register'$this->request->session()->forget(‘register’);}else{//否则抛http异常,跳转到403页面throw new HttpException(403,'请忽重复注册');}//省略下面的验证、注册逻辑等代码}

【补充】如果是参数验证失败,比如手机号已注册之类的,你back()->withInput()->withErrors();是会重新执行showRegistrationForm()方法的,所以出错后再次提交是不会被当做重复提交处理的

简单对其进行封装

<?phpnamespace App\Http\Controllers;use Illuminate\Foundation\Bus\DispatchesJobs;use Illuminate\Routing\Controller as BaseController;use Illuminate\Foundation\Validation\ValidatesRequests;use Illuminate\Foundation\Auth\Access\AuthorizesRequests;use Illuminate\Http\Request;/*** 基础控制器,封装了web及api请求的一些公共方法* @author 94505**/class Controller extends BaseController{use AuthorizesRequests, DispatchesJobs, ValidatesRequests;/*** 请求** @var Request*/protected $request;public function __construct(){$this->request = app('request');}/*** 防止表单重复提交的key前缀* @var string*/private $formResubmitPrefix = 'f_';/*** 将key加个前缀* @param unknown $key* @return string*/private function formResubmitKeyProcess($key){if(empty($key)){//默认使用当前路由的uri为keyreturn $this->formResubmitPrefix.Route::current()->uri;}else{return $this->formResubmitPrefix.$key;}}/*** 在初始化表单前调用(如上面分步实现中的showRegistrationForm()方法中)* @param unknown $key*/protected function formInit($key = null){$key = $this->formResubmitKeyProcess($key);$this->request->session()->put($key,time());}/*** 在处理表单提交的方法中调用(如上面分步实现中的register()方法)* @param string $message* @param unknown $key* @throws HttpException*/protected function formSubmited(string $message = '请忽重复提交!',$key = null){$key = $this->formResubmitKeyProcess($key);if($this->request->session()->has($key)){$this->request->session()->forget($key);}else{throw new HttpException(403,$message);}} /*** 在处理表单提交的方法中调用(如上面分步实现中的register()方 法),该方法方便自定义重复提交时的提示页面,可以在子类中if判断一下,如果发生重复提交,响应自定义的界面* @param string $message* @param unknown $key*/protected function formSubmitIsRepetition(string $message = '请勿重复提交!',$key = null){$key = $this->formResubmitKeyProcess($key);if($this->request->session()->has($key)){$this->request->session()->forget($key);return false;}else{return response()->view('errors.403',['message'=>$message],403);}}/*** 该方法用于ajax请求,返回的数据是数组* @param string $message* @param unknown $key*/protected function formSubmitedForAjax(string $message = '请勿重复提交!',$key = null){$key = $this->formResubmitKeyProcess($key);if($this->request->session()->has($key)){$this->request->session()->forget($key);return false;}else{return ['result'=>'fail','message'=>$message];}}}

在需要防止表单重复提交的控制器内,继承上面封装的Controller就可以直接调用里面的方法了,记得在子类构造方法中调用parent::__construct();,不然$this->request会为null,当然你也可以改成用全局Session辅助函数session()。

3、数据库层面

数据库加unique索引的话只能根据实际情况权衡决定。

比如用户表的手机号(列phone)可用来登陆,必须要求唯一,但在大多数情况下你无法加这个索引,因为现在一般都支持多种登陆方式,如微信登陆、微博登陆,这个手机号可能会没有值,除非程序自动生成一个,但是否有必要?再比如一个varchar类型的列,虽然数据是唯一的,也不会出现空的情况,考虑到varchar类型插入与修改数据时更新索引的性能消耗,可能会放弃加这个索引。

作为一个严谨的程序员,防止表单重复提交处理是必须的!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。