اعتبار سنجی و کنترل تعداد دفعات ارسال فرم در لاراول
سیستم Validation لاراول مجموعه rule های متنوعه ای درخود داره که به واسطه آنها میتونید داده های ووردی را کنترل و validate (اعتبارسنجی) کنید . حال اینکه با وجود تمامی این ابزار ها و rule ها گه گاه شما مجبور میشین rule های جدیدی ایجاد کنید و سیستم ولیدیشن لاراول را اصطلاحا customize (سفارشی) کنین . ما در این جلسه قصد داریم یک rule یا اکستنشن جدید به validator لاراول اضافه کنیم که تا بحال شاید عدم وجود آن بخوبی حس مشید . این اکستنشن توسط تیم لارابوک ساخته شده و با استفاده از آن میتونیم تعداد دفعات ثبت یک فرم ورود اطلاعاتی را کنترل کنیم .قصد داریم تو این پست شما را قدم به قدم با نحوه ایجاد این امکان آشنا کنیم .
اعتبار سنجی
با این امکان ما میتونیم کاربر رو محدود کنیم مثلا هر یک دقیقه بتونه ۱ بار فرم رو ارسال کنه و اگر از این حد فراتر رفت فرمش ولیدیت نشه و اررور بهش بده . برای مثال ما در بازنشانی رمز عبور ، کاربر رو محدود کردیم که هر یک دقیقه یک بار فقط بتونه ایمیل بازنشانی بفرسته و بااین کار از اسپم شدن IP سرور جلوگیری میکنیم .
برای ساخت آن ابتدا ما نیاز به یک جدول داریم که اطلاعات کاربر را درخود ذخیره کنه .
پس ابتدا یک migration ایجاد میکنیم .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateSuccessRatesTable extends Migration { public function up() { Schema::create('success_rates', function (Blueprint $table) { $table->string('hash',32); $table->integer('attempts'); $table->integer('expire'); }); } public function down() { Schema::drop('success_rates'); } } |
سپس برای ارتباط با جدول success_rates مون یک مدل با نام SuccessRate ایجاد میکنیم . (در این مثال ما مدل مون رو تو فولدر app/models قرار دادیم )
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace App\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; class SuccessRate extends Model { protected $table = 'success_rates'; protected $primaryKey = 'hash'; public $timestamps=false; protected $fillable=['hash','expire','attempts']; } |
برای اینکه بخوایم اکستنشن success_rate رو به ولیدیتور اضافه کنیم ، باید برای validator یک resolver بنویسیم که هر زمان در قسمت های پروژه خواستیم سرویس validator رو استفاده کنیم ، بجاش از یک کلاس جایگزین دیگه سرویس گرفته بشه. اصطلاحا میگیم resolve بشه.
برای این منظور ابتدا یک کلاس با نام Validator در فولدر app ایجاد میکنیم .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
<?php namespace App; use App\Models\SuccessRate; use \Illuminate\Validation\Validator as BaseValidator; class Validator extends BaseValidator{ function __construct($translator, array $data, array $rules, array $messages=[], array $customAttributes=[]) { parent::__construct($translator, $data, $rules, $messages, $customAttributes); // add some global variable to validator $this->appendData([ 'request_ip' => app('request')->ip() ]); } /** * Appends array to validator's data * @param array $append */ function appendData(array $append){ $data =(array) $this->getData(); $files =(array) $this->getFiles(); $this->setData(array_merge($data,$files, $append)); } /** * 'request_ip'=> 'success_rate:60,1{,identifire}' * اگر بیش از ۱ درخواست در مدت ۱ دقیقه داشت ولیدیت نمی شود * @return bool */ public function validateSuccessRate($attribute, $user_ip, $parameters) { @list($per_sec, $accept,$id) = $parameters; $key = $this->success_rate_make_hash($id, $user_ip, $per_sec); if(!($result=$this->success_rate_check($key,$per_sec, $accept,'success_rate'))) return false; list($expire,$attempts)=$result; $this->after(function ($validator) use ($key, $expire, $attempts) { if (!$validator->failed()) { $this->success_rate_save($key,$expire,$attempts); } return true; }); return true; } /** * Generates hash Key * @return string */ function success_rate_make_hash(){ $params=func_get_args(); $id = array_shift($params); $id=$id?:app('router')->getCurrentRoute()->getActionName(); return md5($id . implode('',$params)); } /** * Checks which key is expired or not * @param $hash_key * @param $expire * @param $attempt * @param string $messgae_key * @return bool */ private function success_rate_check($hash_key, $per_sec, $accept, $messgae_key='success_rate'){ $expire=0; $attempts=0; if ($last = $this->success_rate_get($hash_key)) list($expire, $attempts) = [$last->expire, $last->attempts]; if (time() <= $expire) { // its not expired yet $attempts++; if ((int)$attempts > $accept) // is not reached the maximum attempt { if ($per_sec < 60) $x = "{$per_sec} ثانیه {$accept} بار"; else $x = intval($per_sec / 60) . " دقیقه {$accept} بار"; $this->setCustomMessages([$messgae_key => "شما تنها هر {$x} مجاز به انجام این کار هستید!"]); return false; // prevent to continue! } } else { $expire = time() + $per_sec; // save new time $attempts = 1; } return [$expire,$attempts]; } function success_rate_save($key, $expire,$attempts){ $data = [ 'hash' => $key, 'expire' => $expire, 'attempts' => $attempts, ]; if ($srate=SuccessRate::find($key)) { return $srate>update($data); }else SuccessRate::create($data); } function success_rate_get($key) { if ($data = SuccessRate::find($key)) return $data; return []; } } |
نکته ای که وجود داره اینه که این کلا داره از کلاس والد Validator ارث بری می کنه یعنی ما میتوانیم به تمامی متد ها و property های کلاس ولیدیتور دسترسی داشته باشیم .
همچنین برای ایجاد rule جدید در سرویس Validator کافیست یک متد در این کلاس ایجاد و ابتدای نام متد را کلمه validate قرار دهید . برای مثال validateSuccessRate که رول success_rate ما را می سازد .
حالا باید یک provider با نام ValidationServiceProvider ایجاد کنیم که کلاس مورد نظرمون رو به عنوان سرویس resolve کنه .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class ValidationServiceProvider extends ServiceProvider { /** * Bootstrap the application services. * * @return void */ public function boot() { $this->app['validator']->resolver(function($translator, $data, $rules, $messages) { return new \App\Validator($translator, $data, $rules, $messages); }); } public function register(){} } |
خوب الان به سراغ فایل config/app.php برید و سرویس که ایجاد کردیم رو به لیست سرویس ها اضافه کیند :
1 |
App\Providers\ValidationServiceProvider', |
و در آخر آنرا به آرایه rule های validation اضافه کنید .
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php $validator = Validator::make($request->all(), [ 'request_ip'=> 'success_rate:60,1', ←--- این خط را اضافه کنید 'title' => 'required', 'body' => 'required', ]); if ($validator->fails()) { ... } |
در مثال بالا ما با اعمال این rule به کاربر اجازه میدهیم که تنها در هر دقیقه ۱ بار فرمش سابمیت شود . نحوه استفاده از این rule کاملا شبیه به میدلور throttle در لاراول می باشد .پس پارامتر اول مدت زمان برحسب ثانیه می باشد و پارامتر دوم تعداد دفعات تایید فرم می باشد که در مدت زمان قید شده قابل قبول است .
نکته دیگر ‘request_ip’ به صورت خودکار به validator اضافه میگردد و مقدارش برابر IP کاربر می باشد و نیاز نیست آن را همراه با داده های validation ارسال نماید !
کلام آخر :
استفاده از ابزار معرفی شده امنیت سیستم شما را در مقابل تهدید های احتمالی تا حدودی تضمین میکنه و تا حد امکان از ایجاد اطلاعات هرز در سیستم و افزونگی داده جلوگیری میکنه . همچنین بدلیل استفاده آسان و در دسترس بودن آن تقریبا میتونیم اون رو در هر جایی از پروژه استفاده کنیم .
همچنین میتونید نظراتتون رو در باره ابزار فوق از طریق فرم پاین صفحه اعلام کنید تا ما بتونیم باگ های احتمالی اون رو هرچه سریعتر برطرف نماییم .
آخرین دیدگاهها