Эта статья была рецензирована Верной Анчетой . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
Laravel Socialite — это пакет, разработанный для отвлечения любых сложностей социальной аутентификации и стандартного кода в свободный и выразительный интерфейс.
Socialite поддерживает только Google, Facebook, Twitter, LinkedIn, Github и Bitbucket в качестве поставщиков OAuth. Они не будут добавлять других в список, однако, существует коллекция сообщества, называемая провайдерами Socialite , которая содержит множество неофициальных провайдеров для Socialite. Подробнее об этом в следующем разделе.
Я предполагаю, что у вас уже есть свежий экземпляр приложения Laravel, запущенный и работающий на вашей машине, так что вы сможете увидеть код в действии. Если вам нужна хорошая среда разработки, вы можете использовать Homestead Improved .
Аутентификация на основе форм
Прежде чем перейти к аутентификации OAuth, давайте настроим стандартную аутентификацию Laravel на основе форм. Для этого мы запускаем команду make:auth
artisan, которая устанавливает все необходимые представления, а также необходимые конечные точки аутентификации.
php artisan make:auth
Примечание. Нам также нужно запустить
php artisan migrate
чтобы убедиться, что таблицаusers
создана.
Теперь, если мы перейдем к /login
, мы должны увидеть хорошую страницу входа в стиле Bootstrap, которая работает.
Добавление социальной аутентификации
Чтобы начать работу с Socialite, мы устанавливаем его с помощью Composer:
composer require laravel/socialite
После установки поставщик услуг и фасад Socialite должны быть зарегистрированы в config/app.php
— как и в любом другом пакете Laravel.
config/app.php
<?php // ... 'providers' => [ // ... /* * Package Service Providers... */ Laravel\Socialite\SocialiteServiceProvider::class, ], // ...
И вот псевдоним фасада:
<?php // ... 'aliases' => [ // ... 'Socialite' => Laravel\Socialite\Facades\Socialite::class, ], // ...
Socialite регистрируется как загружаемый ленивый одноэлементный сервис внутри сервисного контейнера.
конфигурация
Чтобы использовать любого провайдера, нам нужно зарегистрировать приложение OAuth на этой платформе провайдера. В ответ мы получим пару идентификатора клиента и секретных ключей клиента в качестве учетных данных для взаимодействия с API провайдера.
Нам нужно добавить учетные данные в config/services.php
для каждого провайдера:
// ... 'facebook' => [ 'client_id' => env('FB_CLIENT_ID'), 'client_secret' => env('FB_CLIENT_SECRET'), 'redirect' => env('FB_URL'), ], 'twitter' => [ 'client_id' => env('TWITTER_CLIENT_ID'), 'client_secret' => env('TWITTER_CLIENT_SECRET'), 'redirect' => env('TWITTER_URL'), ], 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => env('GITHUB_URL'), ], // ...
Фактические значения ключей помещаются в файл .env
в корневом каталоге проекта.
База данных Соображения
Поскольку структура таблицы users
не была разработана для интеграции социальных аутентификаций, нам сначала нужно сделать несколько настроек там.
Обычно, когда пользователи используют подход социальной аутентификации, им не нужно выбирать пароль, если мы не попросим их сделать это (после авторизации OAuth — не делайте этого ). Кроме того, пользователь может не иметь адрес электронной почты, связанный с соответствующим поставщиком OAuth. Следовательно, нам нужно сделать поля email
и password
nullable
.
Чтобы изменить схему, мы используем построитель схемы Laravel. Перед изменением полей в существующих таблицах нам нужно doctrine/dbal
пакет doctrine/dbal
.
composer require doctrine/dbal
Начнем с users
:
php artisan make:migration prepare_users_table_for_social_authentication --table users
Теперь мы делаем поля nullable
email
и password
nullable
:
Файл:
database/migrations/xxxxxx_prepare_users_table_for_social_authentication.php
<?php // ... /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { // Making email and password nullable $table->string('email')->nullable()->change(); $table->string('password')->nullable()->change(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->string('email')->nullable(false)->change(); $table->string('password')->nullable(false)->change(); }); } // ...
Для хранения связанных социальных учетных записей пользователей мы вместе создаем модель и ее файл миграции:
php artisan make:model LinkedSocialAccount --migration
Файл:
database/migrations/xxxxxx_create_linked_social_accounts_table.php
<?php // ... public function up() { Schema::create('linked_social_accounts', function (Blueprint $table) { $table->increments('id'); $table->bigInteger('user_id'); $table->string('provider_name')->nullable(); $table->string('provider_id')->unique()->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('linked_social_accounts'); } // ...
provider_name
— это имя провайдера, а provider_id
— это идентификатор пользователя на платформе провайдера.
Чтобы применить изменения, мы запускаем migrate
:
php artisan migrate
Модели
У каждого пользователя может быть много связанных социальных учетных записей, что подразумевает связь «один ко многим» между User
и LinkedSocialAccounts
. Чтобы определить это отношение, мы добавляем следующий метод в модель User
:
Файл :
app/User.php
// ... public function accounts(){ return $this->hasMany('App\LinkedSocialAccount'); } // ...
Давайте добавим обратную зависимость этого отношения и в модель LinkedSocialAccount
:
Файл : app/LinkedSocialAccounts.php
<?php // ... public function user() { return $this->belongsTo('App\User'); } // ...
Кроме того, мы делаем LinkedSocialAccounts
массы provider_name
и provider_id
добавлением их в массив $fillable
LinkedSocialAccounts
в LinkedSocialAccounts
.
Файл :
app/LinkedSocialAccounts.php
<?php // ... protected $fillable = ['provider_name', 'provider_id' ]; public function user() { return $this->belongsTo('App\User'); }
Это позволяет нам использовать метод create()
при связывании социальной учетной записи с пользователем.
Контроллеры
Теперь мы создаем контроллер в пространстве имен Auth
. Нам нужно два действия в нашем классе контроллеров, одно для перенаправления пользователя к провайдеру OAuth и другое для получения обратного вызова от провайдера.
php artisan make:controller 'Auth\SocialAccountController'
Мы редактируем класс контроллера и получаем что-то вроде этого:
Файл :
app/Http/Controllers/Auth/SocialAccountController.php
<?php /** * Redirect the user to the GitHub authentication page. * * @return Response */ public function redirectToProvider($provider) { return \Socialite::driver($provider)->redirect(); } /** * Obtain the user information * * @return Response */ public function handleProviderCallback(\App\SocialAccountsService $accountService, $provider) { try { $user = \Socialite::with($provider)->user(); } catch (\Exception $e) { return redirect('/login'); } $authUser = $accountService->findOrCreate( $user, $provider ); auth()->login($authUser, true); return redirect()->to('/home'); } }
В предыдущем коде redirectToProvider()
перенаправляет пользователя на соответствующую конечную точку авторизации, вызывая метод redirect()
провайдера.
<?php // ... return Socialite::driver($provider)->redirect(); // ...
Мы также можем изменить области по умолчанию, используя scopes()
, перед вызовом redirect()
:
<?php // ... return Socialite::driver($provider)->scopes(['users:email'])->redirect(); // ...
Поскольку поведение поставщиков OAuth не всегда предсказуемо, мы используем блок try/catch
чтобы позаботиться о непредвиденных ситуациях. Если все идет, как и ожидалось, без исключения, пользовательский объект (экземпляр Laravel\Socialite\Contracts\User
) извлекается из провайдера. Этот объект предоставляет несколько методов получения для получения информации о пользователе — включая имя, адрес электронной почты, токен доступа и т. Д. Доступные методы можно найти в документации .
Затем мы извлекаем объект локального пользователя (находящийся в нашей таблице users
) или создаем его, если он еще не существует. Для этого мы вызываем findOrCreate()
из вспомогательного класса SocialAccountsService
(этот класс вводится в качестве аргумента в handleProviderCallback()
).
После извлечения объекта пользователя мы регистрируем пользователя, перенаправляя его на страницу панели инструментов.
Теперь давайте создадим наш вспомогательный класс SocialAccountService.php
.
Под пространством имен App
создайте файл со следующим кодом:
Файл :
app/SocialAccountService.php
<?php namespace App; use Laravel\Socialite\Contracts\User as ProviderUser; class SocialAccountService { public function findOrCreate(ProviderUser $providerUser, $provider) { $account = LinkedSocialAccount::where('provider_name', $provider) ->where('provider_id', $providerUser->getId()) ->first(); if ($account) { return $account->user; } else { $user = User::where('email', $providerUser->getEmail())->first(); if (! $user) { $user = User::create([ 'email' => $providerUser->getEmail(), 'name' => $providerUser->getName(), ]); } $user->accounts()->create([ 'provider_id' => $providerUser->getId(), 'provider_name' => $provider, ]); return $user; } } }
Этот класс имеет только одну работу и один метод для создания или получения локального пользователя и связывания с ним социальной учетной записи.
В findOrCreate
мы сначала запрашиваем таблицу linked_social_accounts
чтобы узнать, есть ли какая-либо социальная учетная запись, зарегистрированная с текущим идентификатором провайдера. Если это так, мы возвращаем объект локального пользователя, которому принадлежит эта социальная учетная запись:
<?php // ... if ($account) { return $account->user; } // ...
Если социальная учетная запись не найдена, либо пользователь не существует, либо пользователь еще не связал социальные учетные записи. Имея это в виду, мы осуществляем поиск в таблице users
по электронной почте, поскольку пользователь мог зарегистрироваться в нашей системе через форму регистрации. Если пользователь не найден, мы создаем новую запись пользователя и связываем с ней текущую социальную учетную запись.
Маршруты
Нам нужны два маршрута для нашей функции социальной аутентификации:
Файл :
routes/web.php
<?php // ... Route::get('login/{provider}', 'Auth\SocialAccountController@redirectToProvider'); Route::get('login/{provider}/callback', 'Auth\SocialAccountController@handleProviderCallback');
На указанных выше маршрутах в качестве параметра маршрута указывается provider
. Это позволяет нам повторно использовать эти два маршрута и для других провайдеров.
Пример: аутентификация через Github
Чтобы проверить, что мы создали, давайте добавим Github в качестве опции социальной аутентификации (входа в систему).
Во-первых, нам нужно зарегистрировать новое приложение OAuth на Github.
На странице создания приложения есть несколько полей, которые нам нужно заполнить:
-
Имя приложения должно быть описательным для нашего приложения. Это то, что увидят пользователи при перенаправлении на Github для авторизации нашего приложения.
-
URL домашней страницы — это URL нашего сайта. Это может быть
http://localhost:8000
или любой другой допустимый домен. -
URL обратного вызова авторизации — это конечная точка на нашем веб-сайте, на которую пользователь перенаправляется после завершения авторизации.
После создания приложения мы перенаправлены на страницу редактирования, где можем найти (и скопировать) наши ключи.
конфигурация
На этом этапе мы добавляем наши учетные данные Github в config/services.php
:
Файл :
config/services.php
<?php // ... 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), 'client_secret' => env('GITHUB_CLIENT_SECRET'), 'redirect' => env('GITHUB_URL'), ], // ...
Хотя мы можем напрямую поместить учетные данные и URL-адрес обратного вызова в config/services.php
, мы сохраняем их в файле .env
нашего приложения (и автоматически загружаем их из файла services.php
с помощью getenv()
). Это полезно, когда мы хотим изменить значения в нашей производственной среде, не касаясь кода.
Файл :
.env
GITHUB_CLIENT_ID=API Key GITHUB_CLIENT_SECRET=API secret GITHUB_URL=callbackurl
Добавление ссылки Github на страницу входа
Последняя часть головоломки — добавление ссылки Github на страницу входа. Откройте resources/views/auth/login.blade.php
и поместите следующий код в нужное место.
Файл :
resources/views/auth/login.blade.php
<!-- Login page HTML code --> <a href="/login/github" class="btn btn-default btn-md">Log in with Github</a> <!-- Login page HTML code -->
Это должно выглядеть так:
Если мы нажмем « Войти через Github» , мы будем отправлены на страницу авторизации Github:
Проект социальных провайдеров
Socialite Providers — это проект, управляемый сообществом, который предоставляет множество неофициальных провайдеров для Socialite. Каждый провайдер устанавливается как независимый пакет (через Composer).
Поставщики используют пакет Manager , разработанный в рамках проекта «Поставщики Socialite» и установленный с каждым поставщиком в качестве зависимости, чтобы зарегистрироваться в качестве поставщиков Socialite.
Пакет Manager поставляется с поставщиком услуг Laravel, который расширяет стандартную услугу Socialite. Чтобы использовать поставщиков из коллекции SP, нам нужно заменить этого поставщика услуг на Socialite:
Файл : config/app.php
// ... SocialiteProviders\Manager\ServiceProvider:class, // ...
Примечание: поставщики услуг и поставщики социальных сетей — это две разные концепции с одинаковыми именами, и их не следует путать друг с другом. Поставщик услуг — это класс для регистрации службы в контейнере услуг Laravel, в то время как поставщики Socialite (или просто поставщики) — это классы для взаимодействия с различными поставщиками OAuth.
Каждый поставщик из коллекции поставляется с прослушивателем событий , который необходимо добавить в класс app/Provider/EventServiceProvider
для прослушивания события SocialiteWasCalled
.
При каждом обращении к SocialiteWasCalled
запускается событие SocialiteWasCalled
. Следовательно, все провайдеры, слушающие это событие, зарегистрируются в Socialite (реализуя шаблон наблюдателя ).
Файл :
app/Providers/EventServiceProvider.php
<?php // ... protected $listen = [ \SocialiteProviders\Manager\SocialiteWasCalled::class => [ 'SocialiteProviders\Deezer\DeezerExtendSocialite@handle', ], ];
Приведенный выше пример регистрирует провайдера для аутентификации через Deezer.
Примечание. Стандартные поставщики Socialite все еще могут использоваться, если мы не переопределим их с поставщиком с тем же именем.
Пример: аутентификация через Spotify
В качестве примера давайте добавим Spotify в качестве опции входа.
Сначала мы направляемся к провайдерам Socialite, чтобы найти провайдера для Spotify — в левой боковой панели.
У каждого провайдера есть свое руководство по установке и использованию. Чтобы установить провайдера для Spotify, мы используем Composer:
composer install socialproviders/spotify
конфигурация
Опять же, нам нужно зарегистрировать приложение на платформе разработчика Spotify, чтобы получить наши учетные данные. Как только мы получим ключи, мы добавим их в наши настройки.
Пакет Manager позволяет очень легко настроить параметры для новых провайдеров. В отличие от стандартных провайдеров, нам не нужно добавлять запись в config/services.php
каждого провайдера. Вместо этого мы только добавляем настройки в файл .env
нашего приложения — благодаря вспомогательному классу Manager Config Retriever .
Настройки должны называться CLIENT_ID
, CLIENT_SECRET
и REDIRECT_URL
с префиксом имени поставщика:
Файл:
.env
SPOTIFY_CLIENT_ID = YOUR_CLIENT_ID_ON_SPOTIFY SPOTIFY_CLIENT_SECRET = YOUR_CLIENT_SECRET_ON_SPOTIFY SPOTIFY_REDIRECT_URL = YOUR_CALL_BACK_URL
Вид
Далее мы добавляем ссылку « Войти через Spotify» на страницу входа:
Файл:
resources/views/auth/login.blade.php
<!-- Login page HTML code --> <a href="/login/spotify" class="btn btn-default btn-md">Log in with Spotify</a> <!-- Login page HTML code -->
Страница входа в систему должна выглядеть следующим образом:
Мы можем повторно использовать маршруты, которые мы определили в предыдущем примере (аутентификация через Github) или создать новые маршруты с новыми контроллерами и логикой.
Нажав на « Login with Spotify
, мы будем перенаправлены на страницу авторизации Spotify:
Это значит, что это работает!
Создание собственного провайдера
Мы можем легко создать нашего собственного провайдера, если не найдем определенного провайдера в коллекции Socialite Providers.
Каждый провайдер состоит из двух компонентов:
- Класс провайдера
- Слушатель событий
Класс провайдера
Класс провайдера содержит всю логику для обработки операций, связанных с OAuth.
Если вы также хотите поддерживать OAuth 1.0, вам нужно реализовать для него отдельный класс провайдера.
Чтобы быстро начать, давайте взглянем на класс провайдера Deezer из коллекции SP:
Файл:
vendor/socialiteproviders/deezer/Provider.php
<?php namespace SocialiteProviders\Deezer; use SocialiteProviders\Manager\OAuth2\User; use Laravel\Socialite\Two\ProviderInterface; use SocialiteProviders\Manager\OAuth2\AbstractProvider; class Provider extends AbstractProvider implements ProviderInterface { /** * Unique Provider Identifier. */ const IDENTIFIER = 'DEEZER'; /** * {@inheritdoc} */ protected $scopes = ['basic_access', 'email']; /** * {@inheritdoc} */ protected function getAuthUrl($state) { return $this->buildAuthUrlFromBase( 'https://connect.deezer.com/oauth/auth.php', $state ); } /** * {@inheritdoc} */ protected function getTokenUrl() { return 'https://connect.deezer.com/oauth/access_token.php'; } /** * {@inheritdoc} */ protected function getUserByToken($token) { $response = $this->getHttpClient()->get( 'https://api.deezer.com/user/me?access_token='.$token ); return json_decode($response->getBody()->getContents(), true); } /** * {@inheritdoc} */ protected function mapUserToObject(array $user) { return (new User())->setRaw($user)->map([ 'id' => $user['id'], 'nickname' => $user['name'], 'name' => $user['firstname'].' '.$user['lastname'], 'email' => $user['email'], 'avatar' => $user['picture'], ]); } /** * {@inheritdoc} */ protected function getCodeFields($state = null) { return [ 'app_id' => $this->clientId, 'redirect_uri' => $this->redirectUrl, 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator), 'state' => $state, 'response_type' => 'code', ]; } /** * {@inheritdoc} */ public function getAccessToken($code) { $url = $this->getTokenUrl().'?'.http_build_query( $this->getTokenFields($code), '', '&', $this->encodingType ); $response = file_get_contents($url); $this->credentialsResponseBody = json_decode($response->getBody(), true); return $this->parseAccessToken($response->getBody()); } /** * {@inheritdoc} */ protected function getTokenFields($code) { return [ 'app_id' => $this->clientId, 'secret' => $this->clientSecret, 'code' => $code, ]; } /** * {@inheritdoc} */ protected function parseAccessToken($body) { parse_str($body, $result); return $result['access_token']; } }
Каждый класс провайдера расширяет абстрактный класс Laravel\Socialite\Two\AbstractProvider
. Этот абстрактный класс содержит несколько конкретных методов для обработки общих операций OAuth 2.0 — от форматирования областей до получения и обработки маркеров доступа. Нам просто нужно расширить этот абстрактный класс и реализовать его абстрактные методы.
Кроме того, нам нужно реализовать ProviderInterface
, который определяет два метода для реализации, redirect()
и user()
.
Как вы, вероятно, помните из предыдущего раздела, redirect()
перенаправляет пользователей на страницу авторизации поставщика OAuth, а user()
возвращает экземпляр Laravel\Socialite\Contracts\User
— содержащий информацию о пользователе из платформы провайдера.
Прослушиватель событий провайдера
Прослушиватель событий провайдера — это класс, который регистрирует провайдера как провайдера SocialiteWasCalled
при каждом SocialiteWasCalled
события SocialiteWasCalled
.
Давайте посмотрим на слушателя событий Deezer:
Файл: vendor/socialiteproviders/deezer/DeezerExtendSocialite.php
<?php namespace SocialiteProviders\Deezer; use SocialiteProviders\Manager\SocialiteWasCalled; class DeezerExtendSocialite { /** * Register the provider. * * @param \SocialiteProviders\Manager\SocialiteWasCalled $socialiteWasCalled */ public function handle(SocialiteWasCalled $socialiteWasCalled) { $socialiteWasCalled->extendSocialite( 'deezer', __NAMESPACE__.'\Provider' ); } }
Событие SocialiteWasCalled
имеет метод extendSocialite()
, который принимает класс провайдера в качестве аргумента и регистрирует его в Socialite.
Завершение
Социальная аутентификация никогда не была проще при использовании Laravel. Мы узнали, как аутентифицировать наших пользователей, используя широкий спектр поставщиков OAuth. Мы также узнали, как создать нашего собственного провайдера.
Помимо имени провайдера и идентификатора провайдера, вы можете хранить дополнительную социальную информацию в таблице users
, включая аватары, токены доступа, токены обновления (если таковые имеются), и это только некоторые из них. Вы также можете взаимодействовать с API провайдера или даже выполнять некоторые действия от имени пользователя. Конечно, только если пользователь дал вам разрешение на это.
Полный код этого урока доступен на Github , если вы хотите попробовать его сами.
Если у вас есть какие-либо вопросы по этой теме или мы что-то пропустили, сообщите нам об этом в комментариях ниже!