Элегантная форма входа в админку на Laravel и Sentry

от автора

При создании сайтов приходится некоторое время уделить форме ввода логина и пароля для доступа к панели управления. Для ускорения процесса разработки, хочу поделиться рецептом приготовления простой, удобной и элегантной формы входа в админку.

Статья содержит описание некоторых базовых приемов использования Laravel при разработки сайтов и будет полезна тем, кто начинает осваивать данный фреймворк. Для примера использую Ubuntu 12.04, PostgreSQL 9.3, Nginx 1.1.19, PHP 5.5.7, Composer и свежий проект, созданный с использованием Laravel 4.1. Под управление PostgreSQL крутится база данных examples, к которой имеет доступ пользователь examples c одноименным паролем. Nginx же настроен таким образом, что при обращении по адресу http://examples.loc в браузере открывается главная страницу-заглушка, которая идет с Laravel в комплекте, с надписью «You have arrived.»

Все пути к редактируемым файлам указаны относительно директории проекта.

Окружение

Сначала настраиваю локальное окружение в Laravel. Для этого создаю директорию app/config/local и добавляю в нее файл database.php:

<?php /**  * app/config/local/database.php  */ return array(     'default' => 'pgsql',      'connections' => array(         'pgsql' => array( 			'driver'   => 'pgsql', 			'host'     => 'localhost', 			'database' => 'examples', 			'username' => 'examples', 			'password' => 'examples', 			'charset'  => 'utf8', 			'prefix'   => '', 			'schema'   => 'public', 		),     ), ); 

Прошу Laravel использовать окружение local по умолчанию. Для этого редактирую файл bootstrap/start.php и заменяю строку 'your-machine-name' на '*':

// Фрагмент из bootstrap/start.php $env = $app->detectEnvironment(array(  	'local' => array('*'),  )); 

Подключение Sentry

Ссылку на инструкцию по подключению Sentry можно найти в конце статьи. Кратко, делаю следующее.
Добавляю в composer.json строку "cartalyst/sentry": "2.0.*" в блок require.

// Фрагмент composer.json { 	"name": "laravel/laravel", 	"description": "The Laravel Framework.", 	"keywords": ["framework", "laravel"], 	"license": "MIT", 	"require": { 		"laravel/framework": "4.1.*", 		"cartalyst/sentry": "2.0.*" 	}, ... 

Выполняю команду:
$ composer update

Символ $ набирать не надо

Символ $ означает что, надо набрать в командной строке composer update

Добавляю в список сервис-провайдеров в файле app/config/app.php:
'Cartalyst\Sentry\SentryServiceProvider',

Добавляю в список псевдонимов в файле app/config/app.php:
'Sentry' => 'Cartalyst\Sentry\Facades\Laravel\Sentry',

Выполняю миграции Sentry:
$ php artisan migrate --package=cartalyst/sentry

Публикую конфигурационный файл Sentry:
$ php artisan config:publish cartalyst/sentry

Открываю на редактирование app/config/packages/cartalyst/sentry/config.php. Нахожу в нем строку 'login_attribute' => 'email' и заменяю на 'login_attribute' => 'username', для того чтобы Sentry выполнял аутентификацию пользователя по его логину, а не e-mail.

При выполнении миграций Sentry была создана таблица users, в которой есть поле email, но нет username. Поэтому, чтобы Sentry работал, надо добавить недостающее поле. Для этого создаю миграцию:
$ php artisan migrate:make alter_users_add_username

В директории app/database/migration появится файл имя, которого будет состоять из текущей даты и времени и заканчивается на alter_users_add_username.php. Редактирую его следующим образом:

<?php /**  * app/database/migration/0000_00_00_000000_alter_users_add_username.php  */ use Illuminate\Database\Migrations\Migration;  class AlterUsersAddUsername extends Migration {  	/** 	 * Run the migrations. 	 * 	 * @return void 	 */ 	public function up() 	{         Schema::table('users', function($table)         {             $table->string('username');         }); 	}  	/** 	 * Reverse the migrations. 	 * 	 * @return void 	 */ 	public function down() 	{         Schema::table('users', function($table)         {             $table->dropColumn('username');         }); 	}  } 

Для проверки миграции выполняю:
$ php artisan migrate

Для проверки, что миграция успешно откатывается, выполняю:
$ php artisan migrate:rollback

Создаю еще одну миграцию, которая добавляет в таблицу users запись о суперпользователе:
$ php artisan migrate:make add_user_admin

Нахожу в директории app/database/migrate файл, который заканчивается на add_user_admin.php и редактирую его:

<?php /**  * app/database/migration/0000_00_00_000001_add_user_admin.php  */ use Illuminate\Database\Migrations\Migration;  class AddUserAdmin extends Migration {  	/** 	 * Run the migrations. 	 * 	 * @return void 	 */ 	public function up() 	{         $user = Sentry::createUser(array(             'username' => 'admin',             'email' => 'admin@examples.loc',             'password' => 'password',             'activated' => 1,             'permissions' => array(                 'superuser' => 1,             ),         )); 	}  	/** 	 * Reverse the migrations. 	 * 	 * @return void 	 */ 	public function down() 	{         User::where('username', '=', 'admin')->firstOrFail()->delete(); 	}  } 

Проверяю, как миграции накатываются и откатываются. И перехожу к созданию контроллера, который будет отвечать за аутентификацию пользователей.

Форма входа

В директории app/controllers создаю файл AuthController.php:

<?php /**  * app/controllers/AuthController.php  */ class AuthController extends BaseController {       /**      * Отображает страницу входа      *      * @return Illuminate\View\View      */     public function getLogin()     {         $title = 'Вход';         return View::make('auth.login', compact('title'));     } } 

Метод getLogin() передает заголовок страницы через переменную $title в представление auth.login, которое служит для отображения страницы входа в админку.

Создаю представление auth.login. Для этого в директорию app/views добавляю директорию auth и создаю в ней файл login.blade.php:

/**  * app/views/auth/login.blade.php  */ @extends('layout')  @section('main') <div class="container"> {{ Form::open(array('class' => 'form-signin')) }}      @if (!$errors->isEmpty())     <div class="alert alert-danger">         @foreach ($errors->all() as $error)         <p>{{ $error }}</p>         @endforeach     </div>     @endif      <h2 class="form-signin-heading">{{ $title }}</h2>      {{ Form::text('username', null, array('class' => 'form-control', 'placeholder' => 'Логин')) }}     {{ Form::password('password', array('class' => 'form-control', 'placeholde' => 'Пароль')) }}      <label class="checkbox">         {{ Form::checkbox('remember-me', 1) }} Запомни меня     </label>      {{ Form::submit('Войти', array('class' => 'btn btn-lg btn-primary btn-block')) }}  {{ Form::close() }} </div> @stop 

Шаблон login.blade.php расширяет шаблон layout.blade.php. Поэтому создаю его в директории app/views:

/**  * app/views/layout.blade.php  */ <!DOCTYPE html> <html>     <head>         <meta charset="utf-8">         <title>{{ $title }}</title>          @section('styles')         {{ HTML::style('//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css') }}         {{ HTML::style(URL::asset('styles/base.css')) }}         @show     </head>     <body>         @yield('main')          @section('scripts')         {{ HTML::script('//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js') }}         @show     <body> </html> 

После того, как есть метод контроллера и возвращаемое им представление, необходимо настроить роутинг, чтобы GET запросы, отправляемые по адресу http://examples.loc/login, обрабатывались методом getLogin(). Для этого добавляю в файл app/routes.php следующий код:

// Фрагмент app/routes.php ... Route::group(array('before' => 'guest'), function ()  {     Route::get('login', array(         'as' => 'auth.login',          'uses' => 'AuthController@getLogin'     )); }); 

С помощью Route::get() определяю роут с именем auth.login, который направляет GET запросы по адресу /login методу getLogin() контроллера AuthController.
Также поместил роут auth.login в группы роутов. Перед любым роутом, входящим в данную группу будет выполнятся фильтр guest. Данный фильтр означает, что обработка GET запросов по адресу /login будет происходить только в том случает, если пользователя является гостем, т.е. не авторизован.

Перепишу фильтр guest так, чтобы он использовал Sentry. Для этого в файле app/filters.php изменю код фильтра guest:

// Фрагмент app/filters.php ... Route::filter('guest', function() { 	if (Sentry::check()) return Redirect::to('/'); }); ... 

Теперь можно посмотреть, как выглядит форма входа в браузере. Для этого перехожу по адресу http://examples.loc/login и…

Вижу сообщение об ошибке Call to undefined method Illuminate\Cookie\CookieJar::get().

Легким движением Google выясняю, что Sentry 2.0 совместим с Laravel 4.0, но не совместим 4.1. Хорошо, что уже зарелизился Sentry 2.1. Чтобы избавиться от ошибки, изменяю версию Sentry в composer.json на 2.1:

// Фрагмент composer.json { 	"name": "laravel/laravel", 	"description": "The Laravel Framework.", 	"keywords": ["framework", "laravel"], 	"license": "MIT", 	"require": { 		"laravel/framework": "4.1.*", 		"cartalyst/sentry": "2.1.*" 	}, ... 

Выполняю команду:
$ composer update

Еще раз пытаюсь открыть http://examples.loc/login и вижу форму ввода логина и пароля. Для того чтобы форма выглядела элегантно, надо добавить немного CSS. Для этого в директории public создаю директорию styles и добавляю туда файл base.css:

/**  * public/style/base.css   */ body {     padding-top: 40px;     padding-bottom: 40px;     background-color: #eee; }  .form-signin {     max-width: 330px;     padding: 15px;     margin: 0 auto; }  .form-signin .form-signin-heading, .form-signin .checkbox {     margin-bottom: 10px; }  .form-signin .form-signin-heading {     text-align: center; }  .form-signin .checkbox {     font-weight: normal; }  .form-signin .form-control {     position: relative;     font-size: 16px;     height: auto;     padding: 10px;     -webkit-box-sizing: border-box;     -moz-box-sizing: border-box;     box-sizing: border-box; }  .form-signin .form-control:focus {     z-index: 2; }  .form-signin input[type="text"] {     margin-bottom: -1px;     border-bottom-left-radius: 0;     border-bottom-right-radius: 0; }  .form-signin input[type="password"] {     margin-bottom: 10px;     border-top-left-radius: 0;     border-top-right-radius: 0; } 

Контролеры

Теперь форма отображается красиво, но при нажатии на кнопку «Войти», возвращается ошибка 404. Значит надо добавить в AuthController обработчик POST запросов, отправляемых на адрес /login.

<?php /**  * app/controllers/AuthController.php  */ class AuthController extends BaseController {      /**      * Отображает страницу входа      *      * @return Illuminate\View\View      */     public function getLogin()     {         $title = 'Вход';         return View::make('auth.login', compact('title'));     }      /**      * Аутентифицирует и редиректит в админку      *      * @return Illuminate\Http\RedirectResponse      */     public function postLogin()     {         Input::flash();          try {             $credentials = array(                 'username' => Input::get('username'),                  'password' => Input::get('password')             );             $user = Sentry::authenticate($credentials, Input::get('remember-me'));         } catch (Exception $e) {             return Redirect::to(route('auth.login'))                 ->withErrors(array($e->getMessage()));         }          return Redirect::intended(route('admin'));     }      /**      * Обрабатывает выход      *      * @return Illuminate\Http\RedirectResponse      */     public function getLogout()     {         Sentry::logout();         return Redirect::route('auth.login');     } } 

В методе postLogin() данные, переданные через форму сохраняются в сессии с помощью Input::flash(). В блоке try Sentry пытается аутентифицировать пользователя. Если в форме ввода логина и пароля пользователь установит галочку «Запомни меня», то вторым параметром в Sentry::authenticate() будет передано значение true и Sentry запомнит пользователя в случае успешной аутентификации. Если аутентификация по какой-либо причине не прошла успешно, то в блоке catch метод Redirect::to() отправит нас на страницу ввода логина и пароля в месте с сообщением об ошибке. В случае успешной аутентификации метод Redirect::intended() отправит пользователя на ту страницу, на которую он намеревался зайти, когда был перенаправлен на форму ввода логина и пароля. Если такая страница не задана, то откроется страница по адресу /admin, с которой связан роут с именем admin.

Метод getLogin() самая проста реализация как можно разлогинить пользователя. После выхода пользователь будет перенаправление на страницу ввода логина и пароля.

Прежде чем переходить к настройке роутинга, надо добавить метод, который будет обрабатывать запросы, отправляемые по адресу /admin. Для этого отредактирую файл app/controllers/HomeController.php следующим образом:

<?php /**  *  app/controllers/HomeController.php  */ class HomeController extends BaseController {  	public function showWelcome() 	{ 		return View::make('hello'); 	}  	public function getAdmin() 	{ 		return link_to(route('auth.logout'), 'Выход'); 	}  } 

Метод getAdmin() просто отображает на страницу ссылку «Выход», при нажатии на которою пользователь будет разлогиниваться.

Роутинг

Теперь, чтобы новые методы могли обрабатывать запрос, отредактирую в файл app/routes.php:

/**  * app/routes.php  */ Route::get('/', function() { 	return View::make('hello'); });  Route::group(array('before' => 'guest'), function ()  {     Route::get('login', array(         'as' => 'auth.login',          'uses' => 'AuthController@getLogin'     ));      Route::post('login', array(         'before' => 'csrf',         'uses' => 'AuthController@postLogin'     )); });  Route::group(array('before' => 'auth'), function () {     Route::get('admin', array(         'as' => 'admin',         'uses' => 'HomeController@getAdmin',     ));      Route::get('logout', array(         'as' => 'auth.logout',         'uses' => 'AuthController@getLogout'     )); });  

Первая группа роутов, перед которой выполняется фильтр guest, содержи роутинг для адреса /login для GET и POST запросов. Фильтр guest означает, что по адресу /login будут обрабатываться запросы только от гостей, т.е. неавторизованных пользователей.

Вторая группа роутов, перед которой выполняется фильтр auth, содержит роутинг для адресов /admin и /logout. Фильтр auth означает, что по данным адресам будут обрабатываться запросы только от авторизованных пользователей.

Также необходимо отредактировать фильтр auth, так чтобы он использовал Sentry. Для этого открываю файл app/filters.php изменяю фильтр auth следующим образом:

// Фрагмент app/filters.php ... Route::filter('auth', function() { 	if (!Sentry::check()) return Redirect::guest(route('auth.login')); }); ... 

Теперь можно пробовать логиниться в админку использую логин admin и пароль password. Неавторизованному пользователю не удастся зайти на страницу /admin, она будет доступна, только после успешной аутентификации. Также после аутентификации будет невозможно зайти на страницу /login, так как будет происходить редирект на главную страницу.

Надеюсь данный пример поможет начинающим разработчикам лучше понять некоторые элементы Laravel.

Ссылки по теме

getcomposer.org/doc/00-intro.md#installation-nix
laravel.com/docs/installation#install-laravel
cartalyst.com/manual/sentry/installation/laravel-4

ссылка на оригинал статьи http://habrahabr.ru/post/206908/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *