В конце прошлого лета я задумался над простым способом авторизации пользователей форума в мобильном приложении. Как раз в это время вышла версия Laravel 5.3 вместе с пакетом Laravel Passport, где подобное предлагалось из коробки. Раньше я не работал с OAuth 2, так что начал не спеша разбираться. Решил испытать механизм на крысах, в небольшой игре на Unity про Крысу на Стене. Сама игра — простейший раннер, но механизм авторизации может представлять некоторый интерес, если ранее не сталкивался с этим. Я пользовался официальной документацией и статьей про Passport. На хабре подходящей статьи до сих пор не появилось, поэтому решил сам скомпоновать материал, реализовав для интереса добавление рекордов и базовое взаимодействие с клиентом на Unity. Ввиду моей неторопливости это растянулось почти на год, так что сейчас в примерах используются уже Laravel 5.5 и Unity 2017.1.
В первой части статьи разберёмся, как с помощью токена авторизации добавить рекорд пользователя на сайт.
Примечание
Сразу хотелось бы отметить, что я рассматриваю достаточно упрощенную реализацию — в реальном проекте стоило бы использовать валидацию рекордов, выделить обработку запросов в отдельный контроллер, а не писать прямо в роуте, шифровать токен при сохранении в Unity, а также другое такого плана — статья не ставит себе цели покрывать все такие моменты, поэтому они были мною опущены. Я рассматриваю пошагово, для новичков, но всё-таки предполагаю, что у читателя есть базовые навыки работы с локальным сервером и Unity. Если что-то слишком или недостаточно подробно — пишите в комментариях! Это моя первая техническая статья и я открыт для предложений.
Установка Laravel и Laravel Passport
- Создаём новый проект Laravel, заводим БД под него.
- Устанавливаем глобально вспомогательный установщик Laravel через консоль при помощи Composer
composer global require "laravel/installer"
- Для создания проекта через установщик достаточно набрать команду в формате
laravel new <название проекта>
laravel new ratwall-laravel
Дальнейшие команды выполняем из созданной директории. Заходим туда
cd ratwall-laravel
- В файле
.env
прописываем данные для подключения к базе данных. В этом гайде мы будем работать с SQLite, пропишем
DB_CONNECTION=sqlite DB_DATABASE=/полный/путь/к/папке/проекта/database/database.sqlite
После этого создадим соответствующий пустой файл (командойtouch database/database.sqlite
из консоли или, в случае Windows, из любого редактора файлdatabase.sqlite
в директорииdatabase
) - Теперь можно запустить проект локально командой
php artisan serve
Он будет доступен по адресу http://127.0.0.1:8000/
- Для нашего проекта будет достаточно базовой системы аутентификации на сайте, поэтому запускаем команду
php artisan make:auth
- Добавляем зависимости Laravel Passport через Сomposer.
composer require laravel/passport
- Для версий Laravel ниже 5.5Для версий Laravel ниже 5.5 в файле
config/app.php
необходимо было зарегистрировать соответствующий сервис-провайдер. Для этого в массивproviders
добавляем строку
Laravel\Passport\PassportServiceProvider::class,
Если вы устанавливаете Laravel 5.5 (текущая на момент публикации статьи) или выше — этот пункт можно пропустить.
- Базовая система аутентификации создала свои миграции. Импортируем в базу данных вместе с миграциями Passport.
php artisan migrate
- Теперь можно запустить установщик Passport.
php artisan passport:install
Установщик создаст пару ключей, в дальнейшем мы будем работать с ключом типа Password grant client, так что можно сразу записать ключ для Client ID = 2.
- Добавим в модель пользователя
App\User
трейтLaravel\Passport\HasApiTokens
с методами Passport. Класс будет выглядеть так:
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use Notifiable, HasApiTokens; // ... // оригинальный код // ... }
- Следующий шаг — назначение роутов. Здесь это делается добавлением метода
Passport::routes
в загрузочный метод (boot
) сервис-провайдера
App\Providers\AuthServiceProvider
. Сервис-провайдер будет выглядеть следующим образом:
<?php namespace App\Providers; use Illuminate\Support\Facades\Gate; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Laravel\Passport\Passport; class AuthServiceProvider extends ServiceProvider { // ... // оригинальный код // ... public function boot() { $this->registerPolicies(); Passport::routes(); } }
- Наконец, в файле
config/auth.php
устанавливаем драйвер у гардаapi
какpassport
.
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
Password Grant Token
Есть несколько способов авторизации через Passport. Мы рассмотрим простейший способ авторизации через логин-пароль, посредством Password Grant Token. Этот способ позволяет пользователю заполнить форму авторизации прямо в вашем приложении. При установке Laravel Passport один клиент для этого способа авторизации уже был создан (Password grand client, Client ID = 2), можно использовать его. Для создании нового нужно запустить
php artisan passport:client --password
Введите название для клиента, чтобы завершить создание. Команда выведет Client ID и Client Secret. Запомните их, они понадобятся и в этой статье, и в следующей, при формировании запроса от клиента игры.
Модель рекордов
Создадим модель для будущих рекордов. Ключ-m
означает, что будет также создана соответствующая миграция. При добавлении ключа-c
также будет создан контроллер, но в нашем упрощенном случае это не нужно.
php artisan make:model Record -m
После создании модели нужно подготовить миграцию. Отредактируем файлdatabase/migrations/<дата>_create_records_table.php
. По-умолчанию у миграции прописано создание поляid
(для первичного ключа) и сразу два поля для сохранения времени создания и редактирования записи (timestaps
). Для простейшего хранения данных о рекордах добавим два числовых поля —score
(значение рекорда) иuser_id
(идентификатор пользователя, поставившего рекорд). После этого миграция будет выглядеть следующим образом:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateRecordsTable extends Migration { public function up() { Schema::create('records', function (Blueprint $table) { $table->increments('id'); // Значение рекорда - только положительное $table->integer('score')->unsigned(); // Идентификатор пользователя - поле может быть пустым $table->integer('user_id')->unsigned()->nullable(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('records'); } }
Импортируем в БД с помощью команды
php artisan migrate
Отредактируем саму модельApp\Record
. Добавим методuser()
для связи с моделью пользователя по полюuser_id
.
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Record extends Model { // Пользователь public function user() { return $this->belongsTo(User::class); } }
Контроллер рекордов
Для добавления рекорда в базу создадим два роута для API, практически одинаковых, но один из них предназначен для добавления рекорда анонимным пользователем, а другой — авторизированным. В этом туториале мы не будем писать отдельный контроллер для рекордов, всю обработку вынесем в анонимную функцию роута. Для этого в файлroutes/api.php
добавим:
Route::post('/anonymrecord', function (Request $request) { // Создаем новую запись рекорда $record = new \App\Record(); // Добавляем результат $record->score = $request->get('score'); // Сохраняем запись $record->save(); // Возвращаем сообщение return response()->json([ 'message' => 'Рекорд добавлен!', ], 201); }); Route::middleware('auth:api')->post('/record', function (Request $request) { // Создаем новую запись рекорда $record = new \App\Record(); // Добавляем результат $record->score = $request->get('score'); // Если запись добавил авторизированный пользователь, указываем его $record->user_id = \Auth::id(); // Сохраняем запись $record->save(); // Возвращаем сообщение return response()->json([ 'message' => 'Пользователь '. \Auth::user()->name .' добавил рекорд!', ], 201); });
Здесь мы передаем рекорд в POST-запросе, добавляем его в базу и возвращаем сообщение об успехе. Мы добавили два практически идентичных роута — первый для анонимного добавления, второй для добавления рекорда авторизированным пользователем. Авторизированный пользователь может воспользоваться и анонимным роутом, но в нем не будет корректно обрабатываться информация о пользователе, поэтому в качестве варианта решения мы и разделили роуты. При вызове второго роута анонимным пользователем вас будет перенаправлять на форму авторизации.
В этом файле уже был один роут —user
, для получения информации об авторизированном пользователе. Мы ещё вернемся к нему позже.
Можно проверить работоспособность запроса для добавления анонимного рекорда с помощью http-клиента типа Postman (в дальнейшем примеры именно с ним), передав POST-запрос с заданным числовым значением score в форме по адресу http://127.0.0.1:8000/api/anonymrecord. В ответ мы получимjson
с сообщением, что рекорд добавлен.
ПримечаниеМы добавляем рекорд как есть, подразумевается, что поле score целочисленное и неотрицательное. Думаю, не стоит останавливаться на том, что в реальном проекте это нужно проверять, тем более что в Laravel это можно было бы сделать в специальном классе запроса для того же рекорда. Кроме того, стоило бы вынести добавление в класс-контроллер (при необходимости выделив запись в отдельный класс репозитория), чтобы избавиться от дублирования кода.
Для отображения добавленных рекордов на сайте напишем роут вroutes/web.php
с запросом всех рекордов из базы. Он использует шаблонrecords.blade.php
, которое мы создадим в следующем шаге.
Route::get('/records', function () { $records = \App\Record::all(); return view('records', compact('records')); });
Представление рекордов
Создадим шаблонresources/views/records.blade.php
для отображения таблицы рекордов.
@extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">Рекорды</div> <div class="panel-body"> <table class="table table-striped table-hover"> <tr> <th>Пользователь</th> <th>Рекорд</th> <th>Дата</th> </tr> @foreach ($records as $record) <tr> <td>{{ $record->user ? $record->user->name : 'Аноним' }}</td> <td>{{ $record->score }}</td> <td>{{ $record->created_at->diffForHumans() }}</td> </tr> @endforeach </table> </div> </div> </div> </div> </div> @endsection
Если рекорд добавил авторизированный пользователь, выводим его имя, иначе — Аноним.
Теперь по адресу http://127.0.0.1:8000/records нам доступна таблица рекордов — пустая или с теми записями, что мы добавили через Postman.
Эти рекорды добавлялись от Анонима, т.к. мы обращались к роуту анонимного добавления, а не к роуту авторизированного пользователя с токеном авторизации через Postman. Займемся вторым случаем.
Авторизация через OAuth 2
Зарегистрируемся на сайте, используя стандартный механизм Laravel, перейдя по адресу http://127.0.0.1:8000/register. Для примера я указал e-mail habr@habrahabr.ru, пароль habrahabr, имя Habr.
Для того, чтобы получить токен авторизации через Password Grant Token, нужно отправить POST-запрос на адрес http://127.0.0.1:8000/oauth/token. В тело запроса нужно добавить следующие данные:
grant_type
— указываем тип password, т.к. мы используем Password Grant Token;client_id
— идентификатор Client ID из раздела Password Grant Token;client_secret
— ключ Client Secret, из раздела Password Grant Token;username
— в качестве логина для авторизации в базовом функционале Laravel используется e-mail;password
— пароль,scope
— области применения токена (для задания разрешений), оставим *, т.е. все.
Отправим такой запрос через Postman. Если всё заполнено правильно, в ответ мы получим токен обновления, токен авторизации, время действия токена в секундах и тип токена — Bearer. Нам нужен токен авторизации — Access Token.
Проверим токен в действии, получив по нему информации об авторизированном пользователе. Выполним GET-запрос через Postman по адресу http://127.0.0.1:8000/api/user. При этом в раздел Headers нового запроса нужно добавить ключAuthorization
со значениемBearer <токен авторизации>
(то есть тип токена, пробел, сам токен). В результате мы получим информацию о пользователе, которому соответствует этот токен авторизации.
Добавление рекорда авторизированным пользователем
Теперь можно выполнить POST-запрос для добавления рекорда авторизированным пользователем через Postman. Заполним Headers нового POST-запроса аналогично последнему, в качестве адреса используем http://127.0.0.1:8000/api/record. В ответном сообщении теперь отображается имя пользователя, которое соответствует переданному токену.
Если зайти на страницу рекордов, можно увидеть, что у последнего добавленного рекорда стоит имя пользователя.
В следующей части будем работать с нашим небольшим API уже из Unity.
Готовый проект можно скачать на гитхабе. - Устанавливаем глобально вспомогательный установщик Laravel через консоль при помощи Composer