Используем капчу в Laravel
Доброго времени суток. В нескольких статьях по Laravel мы рассмотрели регистрацию пользователей на сайте : «Регистрация, активация и авторизация в Laravel (часть 1)», «Регистрация, активация и авторизация в Laravel (часть 2)» и «Регистрация по инвайтам в Laravel». Но ни в одной статье мы не рассмотрели защиту от ботов. Один из самых простых и эффективных способов защиты от ботов – это капча. Думаю, что такое капча объяснять не надо. Давайте исправим данное недоразумение и установим при регистрации пользователя на сайте под управлением Laravel проверку капчи.
Мы будем использовать пакет mews/captcha. На github.
Установка капчи.
Для установки пакета mews/captcha выполним следующую команду:
composer require mews/captcha
Следующим шагом нам необходимо прописать провайдер и алиас для капчи. Для этого открываем файл config/app.php и добавляем следующие строки
'providers'=>[ // ... Mews\Captcha\CaptchaServiceProvider::class\, ]; 'aliases'=>[; // ... 'Captcha'=>Mews\Captcha\Facades\Captcha::class, ];
После установки переходим к конфигурированию
Конфигурация капчи
Первое, что нам необходимо сделать – это создать файл конфигурации капчи. Для этого вводим следующую команду:
artisan vendor:publish
В папке config у нас появился файл captcha.php, в котором и находится конфигурация нашей капчи.
В настройках ничего трогать не будем.
Теперь мы можем использовать капчу.
Использование капчи
За основу возьмем уже написанную нами регистрацию в статье «Регистрация по инвайтам в Laravel»
Откроем файл с регистрационной формой resources/views/auth/register.blade.php сейчас в нем следующий код:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Регистрация</title> <link href="{{ asset('/css/bootstrap.css') }}" rel="stylesheet"> <script src="{{ asset('/js/jquery.js') }}" type="text/javascript" charset="utf-8"></script> <script src="{{ asset('/js/bootstrap.js') }}" type="text/javascript" charset="utf-8"></script> </head> <body> <div class="container"> <nav class="navbar" role="navigation"> <ul class="nav navbar-nav"> <li class="active"> <a href="{{ url('auth/login') }}">Вход</a> </li> </ul> </nav> {{--Ошибки --}} @if ($errors->has()) <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="alert alert-danger" role="alert"> <button class="close" aria-label="Close" data-dismiss="alert" type="button"> <span aria-hidden="true">×</span> </button> <ul> @foreach($errors->all() as $error) <li> {{{ $error }}} </li> @endforeach </ul> </div> </div> </div> @endif <form role="form" method="post" action="{{ url(App::getLocale().'/auth/register') }}"> {!! csrf_field() !!} <div class="form-group"> <label for="email">Email</label> <input type="email" class="form-control" id="email" placeholder="Email" name='email'> </div> <div class="form-group"> <label for="invite">Код</label> <input type="text" class="form-control" id="invite" placeholder="код" name='invite'> </div> <div class="form-group"> <label for="password">Пароль</label> <input type="password" class="form-control" id="password" placeholder="Пароль" name="password"> </div> <div class="form-group"> <label for="confirm_password">Повторите пароль</label> <input type="password" class="form-control" id="confirm_password" placeholder="Повторите пароль" name="password_confirmation"> </div> <button type="submit" class="btn btn-default">Отправить</button> </form> </div> </body> </html>
Для того, чтобы вставить ссылку на картинку капчи используем функцию captcha_src(). Изменим наш файл следующим образом:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Регистрация</title> <link href="{{ asset('/css/bootstrap.css') }}" rel="stylesheet"> <script src="{{ asset('/js/jquery.js') }}" type="text/javascript" charset="utf-8"></script> <script src="{{ asset('/js/bootstrap.js') }}" type="text/javascript" charset="utf-8"></script> </head> <body> <div class="container"> <nav class="navbar" role="navigation"> <ul class="nav navbar-nav"> <li class="active"> <a href="{{ url('auth/login') }}">Вход</a> </li> </ul> </nav> {{--Ошибки --}} @if ($errors->has()) <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="alert alert-danger" role="alert"> <button class="close" aria-label="Close" data-dismiss="alert" type="button"> <span aria-hidden="true">×</span> </button> <ul> @foreach($errors->all() as $error) <li> {{{ $error }}} </li> @endforeach </ul> </div> </div> </div> @endif <form role="form" method="post" action="{{ url(App::getLocale().'/auth/register') }}"> {!! csrf_field() !!} <div class="form-group"> <label for="email">Email</label> <input type="email" class="form-control" id="email" placeholder="Email" name='email'> </div> <div class="form-group"> <label for="password">Пароль</label> <input type="password" class="form-control" id="password" placeholder="Пароль" name="password"> </div> <div class="form-group"> <label for="confirm_password">Повторите пароль</label> <input type="password" class="form-control" id="confirm_password" placeholder="Повторите пароль" name="password_confirmation"> </div> <div class="form-group"> <label for=""></label> <img src="{{ captcha_src() }}" alt="captcha" class="captcha-img" data-refresh-config="default"><a href="#" id="refresh"><span class="glyphicon glyphicon-refresh"></span></a></p> </div> <div class="form-group"> <label>Капча</label> <input class="form-control" type="text" name="captcha"/> </div> <button type="submit" class="btn btn-default">Отправить</button> </form> </div> </body> </html>
Мы удалили поле для ввода кода (инвайт), и добавили картинку капчи, и поле ввода кода капчи.
Проверим, что у нас получилось:
Отлично, теперь нам надо поправить контроллер регистрации. Добавить проверку капчи.
Открываем файл app/Http/Controllers/Auth/AuthController.php
<?php namespace App\Http\Controllers\Auth; use App\Code; use App\Http\Controllers\CodeController; use App\Http\Controllers\Controller; use App\Invite; use App\User; use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers; use Illuminate\Foundation\Auth\ThrottlesLogins; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Mail; use Validator; class AuthController extends Controller { /* |-------------------------------------------------------------------------- | Registration Login Controller |-------------------------------------------------------------------------- | | This controller handles the registration of new users, as well as the | authentication of existing users. By default, this controller uses | a simple trait to add these behaviors. Why don't you explore it? | */ use AuthenticatesAndRegistersUsers, ThrottlesLogins; /** * Create a new authentication controller instance. * * @return void */ public function __construct() { $this->middleware('guest', ['except' => 'getLogout']); } /** * Get a validator for an incoming registration request. * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ 'email' => 'required|email|max:255|unique:users', 'password' => 'required|confirmed|min:6', 'invite' => 'required|Invite', ]); } /** * Create a new user instance after a valid registration. * * @param array $data * @return User */ protected function create(array $data) { return User::create([ 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); } public function postRegister(Request $request) { $validator = $this->validator($request->all()); if ($validator->fails()) { $this->throwValidationException( $request, $validator ); } $user = $this->create($request->all()); //создаем код и записываем код $code = CodeController::generateCode(8); Code::create([ 'user_id' => $user->id, 'code' => $code, 'type' =>1, ]); //Генерируем ссылку и отправляем письмо на указанный адрес $url = url('/').'/auth/activate?id='.$user->id.'&code='.$code; Mail::send('emails.registration', array('url' => $url), function($message) use ($request) { $message->to($request->email)->subject('Registration'); }); return 'Регистрация прошла успешно, на Ваш email отправлено письмо со ссылкой для активации аккаунта'; } public function activate(Request $request) { $res = Code::where('user_id',$request->id) ->where('code',$request->code) ->first(); if($res) { //Удаляем использованный код $res->delete(); //активируем аккаунт пользователя User::find($request->id) ->update([ 'activated'=>1, ]); //редиректим на страницу авторизации с сообщением об активации return redirect()->to('auth/login')->with(['message' => 'ok']); } return abort(404); } public function postLogin(Request $request) { if (Auth::attempt(['email' => $request->email, 'password' => $request->password, 'activated' => 1])){ return "Редирект в кабинет"; } return redirect()->to('auth/login'); } }
В методе validator уберем проверку invite и добавим проверку капчи:
protected function validator(array $data) { return Validator::make($data, [ 'email' => 'required|email|max:255|unique:users', 'password' => 'required|confirmed|min:6', 'captcha' => 'required|captcha', ]); }
Проверим, что у нас получилось. Попробуем зарегистрироваться, введя заведомо неверную капчу.
Капча введена неверно – отработало правило валидации капчи. Единственное давайте добавим сообщение об ошибке. Для этого откроем файл resources/lang/ru/validation.php и в массив добавим следующий элемент:
'captcha' => 'Неверная капча',
Теперь зарегистрируемся:
Как помните при регистрации у нас отправлялось письмо для активации аккаунта.
Отлично, все работает. И на этом можно завершать. Но есть один маленький нюанс. Не всегда капчу можно распознать и хотелось бы сделать так, что при клики на картинке обновления рядом с капчей капча обновилась.
Для этого в наш файл с формой регистрации добавим небольшой скрпит:
<script> $('#refresh').on('click',function(){ var captcha = $('img.captcha-img'); var config = captcha.data('refresh-config'); $.ajax({ method: 'GET', url: '/get_captcha/' + config, }).done(function (response) { captcha.prop('src', response); }); }); </script>
Данный скрипт с помощью аякса методом get передает данные по ссылке. За этой ссылкой надо закрепить контроллер и метод которые вернут обновленную капчу. Поехали…
Пропишем роут. Для этого открываем файл app/Http/routes.php и добавляем в него следующий роут:
Route::get('/get_captcha/{config?}', function (\Mews\Captcha\Captcha $captcha, $config = 'default') { return $captcha->src($config); });
Ну вот и все. Давайте проверим. Кликаем по картинке обновления и капча обновляется.
Отличные статьи! Пишите больше про Ларавел. Уже вышел релиз по версии 5,2, хотелось бы узнать о ней побольше, в чем отличия, новый функционал и пр.
Спасибо. Хотелось бы больше писать, но времени не хватает
Здравствуйте! Спасибо за статью. Проблема с генерацией самой картинки капчи. {!! captcha_img() !!}.
Вместо картинки квадратик синего цвета.