Статьи

2FA в Laravel с Google Authenticator — получите безопасность!

Эта статья была рецензирована Джадом Битаром , Никласом Келлером , Марко Пиветтой и Энтони Чемберсом . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

векторное изображение с замком и цифровыми символами, указывающими на безопасность

Злоумышленник может получить пароль пользователя многими способами. Это может произойти с помощью социальной инженерии , регистрации ключей или с помощью других гнусных способов. Одних паролей недостаточно для защиты пользователей от взлома их учетных записей, особенно если злоумышленник каким-то образом имеет свои учетные данные.

Чтобы обойти этот серьезный недостаток, была разработана концепция двухфакторной аутентификации (2FA). Пароль, который является одним из факторов, недостаточно для аутентификации пользователя. Понятие состоит в том, что пользователь должен использовать то, что у него есть (первый фактор) и что-то, что он знает (второй фактор), для аутентификации. Пароль — это то, что пользователь знает. Для части «что-то, что у них есть» можно использовать много вещей. В некоторых решениях используются биометрические параметры, такие как отпечатки пальцев, распознавание голоса или сканирование радужной оболочки глаза. Это относительно дорогие решения. Другие методы аутентификации второго фактора включают одноразовые пароли (OTP). Это пароли, которые генерируются на устройстве и подходят для однократного использования. Как правило, существует два типа OTP; счетчик и время. Использование 2FA лучше, чем просто имя пользователя и пароль, потому что злоумышленнику очень трудно получить как пароль, так и второй фактор.

В этом руководстве мы будем использовать Laravel и Google Authenticator, чтобы продемонстрировать, как реализовать 2FA в веб-приложении. Google Authenticator — это всего лишь одна реализация алгоритма одноразового пароля на основе времени (TOTP), RFC 6238 . Этот отраслевой стандарт используется во многих различных решениях 2FA. Google Authenticator имеет некоторые преимущества перед некоторыми другими решениями 2FA, представленными на рынке. После загрузки приложения на смартфон вы можете использовать его в автономном режиме. Многие другие решения 2FA должны быть как-то связаны; они отправляют SMS-сообщение, push-уведомление или даже звонят на смартфон с записанным сообщением. Это плохо для пользователей, которые могут находиться в местах, где их телефон отключен от внешнего мира, например, в офисе, расположенном в подвале здания.

Принцип работы TOTP заключается в том, что сервер генерирует секретный ключ. Этот секретный ключ затем передается пользователю. Секретный ключ используется в сочетании с текущей меткой времени Unix для генерации шестизначного числа с использованием алгоритма на основе кода аутентификации сообщения с хеш-кодом (HMAC). Этот шестизначный номер является OTP. Он меняется каждые 30 секунд.

Настроить

усадьба

Эта статья предполагает, что Laravel Homestead установлен. Использовать его необязательно, но команды могут немного отличаться, если вы используете другую среду и требует PHP 7. Если вы не знакомы с Homestead и хотите получить схожие результаты, как и эта статья, пожалуйста, посетите эту статью SitePoint. это показывает, как настроить Homestead.

Композитор

Создайте новый проект Laravel.

composer create-project --prefer-dist laravel/laravel Project 

Убедитесь, что вы идете в папку проекта

 cd Project 

Существует пакет Laravel, который включает PHP-версию Google Authenticator. Мы будем использовать его в этом проекте. Во-первых, мы включим пакет Laravel Антонио Карлоса Рибейро с помощью Composer. Мы также установим библиотеку для кодирования Base32 в постоянное время.

 composer require pragmarx/google2fa composer require paragonie/constant_time_encoding 

После того, как Composer установил пакет, мы должны сообщить об этом Laravel. Откройте config/app.php и добавьте PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class, в массив providers . Также добавьте 'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class, в массив aliases .

подмости

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

 php artisan make:auth 

Мы изменим часть автоматически сгенерированного кода, чтобы добавить двухфакторную аутентификацию.

База данных и модели

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

 php artisan make:migration add_google2fa_secret_to_users 

Откройте только что созданный файл миграции, расположенный в папке database/migrations . Он будет назван примерно как 2016_01_06_152631_add_google2fa_secret_to_users.php . Замените содержимое файла с кодом ниже:

 <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddGoogle2faSecretToUsers extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function ($table) { $table->string('google2fa_secret')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function ($table) { $table->dropColumn('google2fa_secret'); }); } } 

Когда миграция запущена, столбец google2fa_secret добавляется в таблицу пользователей. Когда откат переносится, этот столбец удаляется из таблицы.

Далее мы запустим миграцию, чтобы настроить таблицы базы данных.

 php artisan migrate 

Теперь, когда столбец google2fa_secret был добавлен в таблицу users , мы должны обновить модель App\User чтобы сделать ее немного более безопасной. По умолчанию, если программа преобразует данные из экземпляра App\User в JSON, содержимое столбца google2fa_secret будет частью объекта JSON. Мы заблокируем это действие. Откройте модель App\User , app/User.php и добавьте элемент массива google2fa_secret в виде строки к hidden атрибуту.

Маршруты

Откройте файл веб- routes/web.php и добавьте следующие маршруты внизу:

 Route::get('/2fa/enable', 'Google2FAController@enableTwoFactor'); Route::get('/2fa/disable', 'Google2FAController@disableTwoFactor'); Route::get('/2fa/validate', 'Auth\AuthController@getValidateToken'); Route::post('/2fa/validate', ['middleware' => 'throttle:5', 'uses' => 'Auth\AuthController@postValidateToken']); 

Маршруты, которые мы добавили, делают разные вещи.

Когда пользователь переходит в /2fa/enable , он создает секретный ключ /2fa/enable и предоставляет инструкции о том, как настроить свое устройство для его использования. Когда пользователь переходит в /2fa/disable , он удаляет секретный ключ /2fa/disable из учетной записи пользователя, чтобы он мог войти в систему, не предоставляя одноразовый пароль. После того, как пользователь аутентифицируется с помощью своего имени пользователя и пароля, если у него настроен секретный ключ /2fa/validate , он будет перенаправлен в /2fa/validate через HTTP GET. Этот маршрут показывает форму, в которой они могут ввести свой одноразовый пароль. Когда пользователь отправляет форму, он отправляется в /2fa/validate через HTTP POST. Этот маршрут гарантирует, что одноразовый пароль, переданный пользователем, действителен. Маршрут также использует промежуточное программное обеспечение throttle Laravel. Если у злоумышленника есть ваш пароль, все комбинации 6-значного токена легко пройти автоматически. Дроссель настроен на максимум 5 запросов в минуту. Промежуточное программное обеспечение Laravel основано на IP-адресе. Эта статья просто показывает вам концепцию регулирования и не защищает от атаки с использованием грубой силы. В производственной реализации рассмотрите возможность регулирования на основе пользователя.

Контроллеры

Мы создадим новый контроллер для включения и отключения поддержки 2FA для пользователя:

 php artisan make:controller Google2FAController 

Откройте новый контроллер Google2FAController и замените содержимое с кодом ниже:

 <?php namespace App\Http\Controllers; use Crypt; use Google2FA; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Illuminate\Foundation\Validation\ValidatesRequests; use \ParagonIE\ConstantTime\Base32; class Google2FAController extends Controller { use ValidatesRequests; /** * Create a new authentication controller instance. * * @return void */ public function __construct() { $this->middleware('web'); } /** * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function enableTwoFactor(Request $request) { //generate new secret $secret = $this->generateSecret(); //get user $user = $request->user(); //encrypt and then save secret $user->google2fa_secret = Crypt::encrypt($secret); $user->save(); //generate image for QR barcode $imageDataUri = Google2FA::getQRCodeInline( $request->getHttpHost(), $user->email, $secret, 200 ); return view('2fa/enableTwoFactor', ['image' => $imageDataUri, 'secret' => $secret]); } /** * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function disableTwoFactor(Request $request) { $user = $request->user(); //make secret column blank $user->google2fa_secret = null; $user->save(); return view('2fa/disableTwoFactor'); } /** * Generate a secret key in Base32 format * * @return string */ private function generateSecret() { $randomBytes = random_bytes(10); return Base32::encodeUpper($randomBytes) } } 

Метод enableTwoFactor() выполняется, когда пользователь хочет настроить 2FA. Во-первых, он генерирует новый секретный ключ. К сожалению, в настоящее время функция генерации ключей внутри библиотеки 2FA не использует криптографически безопасный PRNG , поэтому мы используем random_bytes() для создания байтов для нашего секретного ключа, а Base32 его кодирует. Затем метод сохраняет зашифрованную версию секретного ключа в таблице пользователей.

Затем создается изображение штрих-кода QR размером 200×200 пикселей, содержащее секретный ключ и другие метаданные, необходимые для мобильного приложения Google Authenticator. Это изображение закодировано в схеме Data URI , поэтому мы можем встроить его в веб-страницу. Затем enableTwoFactor представление enableTwoFactor для отображения штрих-кода QR и секретного ключа в виде текста, на случай, если они используют совместимое с RFC 6238 приложение, которое не может считывать штрих-коды QR.

Метод disableTwoFactor() вызывается, когда пользователь хочет отключить 2FA. Он помещает null значение в базу данных для секретного ключа, а затем отображает представление, которое говорит, что 2FA был отключен.

Теперь мы сделаем несколько модификаций в контроллере Auth. Откройте app/Http/Controllers/Auth/AuthController.php и поместите следующие строки кода вверху в разделе use :

 use Cache; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Contracts\Auth\Authenticatable; use App\Http\Requests\ValidateSecretRequest; 

После аутентификации пользователя контроллер Auth видит, существует ли метод authenticated() . Если это так, контроллер выполняет этот метод. По умолчанию леса не создают метод authenticated() . Мы создадим этот метод и используем его, чтобы предложить кому-то ввести одноразовый пароль, если он настроил секретный ключ 2FA. Внутри того же класса AuthController добавьте следующий метод:

  /** * Send the post-authentication response. * * @param \Illuminate\Http\Request $request * @param \Illuminate\Contracts\Auth\Authenticatable $user * @return \Illuminate\Http\Response */ private function authenticated(Request $request, Authenticatable $user) { if ($user->google2fa_secret) { Auth::logout(); $request->session()->put('2fa:user:id', $user->id); return redirect('2fa/validate'); } return redirect()->intended($this->redirectTo); } из  /** * Send the post-authentication response. * * @param \Illuminate\Http\Request $request * @param \Illuminate\Contracts\Auth\Authenticatable $user * @return \Illuminate\Http\Response */ private function authenticated(Request $request, Authenticatable $user) { if ($user->google2fa_secret) { Auth::logout(); $request->session()->put('2fa:user:id', $user->id); return redirect('2fa/validate'); } return redirect()->intended($this->redirectTo); } 

Если пользователь настроил 2FA, этот метод выходит из системы, сохраняет его идентификатор пользователя в сеансе и перенаправляет его на страницу, где он может ввести одноразовый пароль. Если у пользователя нет настроенного 2FA, он перенаправляет их на домашнюю страницу без выхода из системы.

Метод getValidateToken() отображает страницу, на которой пользователь может ввести свой одноразовый пароль. В классе AuthController добавьте этот метод:

  /** * * @return \Illuminate\Http\Response */ public function getValidateToken() { if (session('2fa:user:id')) { return view('2fa/validate'); } return redirect('login'); } 

Если сеанс с идентификатором пользователя не существует, пользователь перенаправляется на страницу входа. Если в сеансе установлен идентификатор пользователя, отображается представление.

Когда пользователь нажимает кнопку «Проверить» на странице одноразового пароля, запрос направляется в метод postValidateToken() . Добавьте следующий код в класс AuthController :

  /** * * @param App\Http\Requests\ValidateSecretRequest $request * @return \Illuminate\Http\Response */ public function postValidateToken(ValidateSecretRequest $request) { //get user id and create cache key $userId = $request->session()->pull('2fa:user:id'); $key = $userId . ':' . $request->totp; //use cache to store token to blacklist Cache::add($key, true, 4); //login and redirect user Auth::loginUsingId($userId); return redirect()->intended($this->redirectTo); } 

Перед запуском этого метода проверка выполняется с помощью класса запроса формы ValidateSecretRequest . Если проверка не postValidateToken() метод postValidateToken() не выполняется. После проверки одноразовый пароль добавляется в черный список на 4 минуты. Это сделано потому, что библиотека 2FA позволяет использовать коды до 4 минут, и мы не хотим, чтобы пользователь мог повторно использовать код в этом окне. Далее пользователь входит в систему и перенаправляется на домашнюю страницу.

Наконец, убедитесь, что в классе AuthController свойство redirectTo имеет значение /home . Это перенаправляет пользователей на домашнюю страницу /home домашнюю страницу вместо страницы приветствия / после входа в систему.

Форма запроса

Чтобы создать новый класс запроса формы, выполните следующую команду:

 php artisan make:request ValidateSecretRequest 

Откройте app/Http/Requests/ValidateSecretRequest.php и замените содержимое следующим кодом:

 <?php namespace App\Http\Requests; use Cache; use Crypt; use Google2FA; use App\User; use App\Http\Requests\Request; use Illuminate\Validation\Factory as ValidatonFactory; class ValidateSecretRequest extends Request { /** * * @var \App\User */ private $user; /** * Create a new FormRequest instance. * * @param \Illuminate\Validation\Factory $factory * @return void */ public function __construct(ValidatonFactory $factory) { $factory->extend( 'valid_token', function ($attribute, $value, $parameters, $validator) { $secret = Crypt::decrypt($this->user->google2fa_secret); return Google2FA::verifyKey($secret, $value); }, 'Not a valid token' ); $factory->extend( 'used_token', function ($attribute, $value, $parameters, $validator) { $key = $this->user->id . ':' . $value; return !Cache::has($key); }, 'Cannot reuse token' ); } /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { try { $this->user = User::findOrFail( session('2fa:user:id') ); } catch (Exception $exc) { return false; } return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'totp' => 'bail|required|digits:6|valid_token|used_token', ]; } } 

Мы делаем три вещи в этом файле;

  1. Установка двух пользовательских правил валидатора
  2. Убедиться, что пользователь авторизован для выполнения этого запроса
  3. Определение правил проверки для этого запроса.

В конструкторе класса мы устанавливаем пользовательские правила валидатора. Первое правило гарантирует, что пользователь представил действительный токен TOTP. Второе правило гарантирует, что пользователь не отправил токен в черный список. Обычно настраиваемые правила валидатора используются для множества разных запросов и определяются в классе AppServiceProvider . Но, поскольку эти правила используются только в одном запросе, мы помещаем его в конструктор этого класса.

Метод authorize() проверяет и проверяет, может ли он получить запись пользователя на основе идентификатора пользователя, сохраненного в сеансе. Если это не работает, то запрос не считается авторизованным.

Метод rules() определяет правила проверки. Правила гласят, что токен не должен быть пустым, быть шестизначным числом, быть действительным и не быть в черном списке пользователя.

Взгляды

На главной странице нам нужно добавить раздел, который позволяет пользователю включить или отключить 2FA. Откройте resources/views/home.blade.php и добавьте следующий код непосредственно перед последним закрывающим тегом div :

  <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">Two-Factor Authentication</div> <div class="panel-body"> @if (Auth::user()->google2fa_secret) <a href="{{ url('2fa/disable') }}" class="btn btn-warning">Disable 2FA</a> @else <a href="{{ url('2fa/enable') }}" class="btn btn-primary">Enable 2FA</a> @endif </div> </div> </div> </div> 

Когда у пользователя не установлено поле google2fa_secret , отображается кнопка, позволяющая ему включить 2FA. Когда google2fa_secret поле google2fa_secret , отображается кнопка, позволяющая отключить 2FA.

Для этого проекта мы создадим несколько новых видов. Мы разместим их в своем собственном каталоге. Создайте каталог 2fa в папке views :

 mkdir resources/views/2fa 

Давайте создадим страницу, которая отображает секретный ключ с инструкциями по настройке устройства 2FA пользователя. Создайте файл представления enableTwoFactor.blade.php во вновь созданной папке resources/views/2fa и поместите в него следующее содержимое:

 @extends('layouts.app') @section('content') <div class="container spark-screen"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">2FA Secret Key</div> <div class="panel-body"> Open up your 2FA mobile app and scan the following QR barcode: <br /> <img alt="Image of QR barcode" src="{{ $image }}" /> <br /> If your 2FA mobile app does not support QR barcodes, enter in the following number: <code>{{ $secret }}</code> <br /><br /> <a href="{{ url('/home') }}">Go Home</a> </div> </div> </div> </div> </div> @endsection 

Если пользователь решает отключить 2FA, создайте страницу, чтобы сообщить ему, что она отключена. Создайте файл disableTwoFactor.blade.php в disableTwoFactor.blade.php resources/views/2fa и поместите в него следующий код:

 @extends('layouts.app') @section('content') <div class="container spark-screen"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">2FA Secret Key</div> <div class="panel-body"> 2FA has been removed <br /><br /> <a href="{{ url('/home') }}">Go Home</a> </div> </div> </div> </div> </div> @endsection 

После того, как пользователь аутентифицируется по электронной почте и паролю, нам нужна страница, где пользователь может ввести свой одноразовый пароль, созданный на его устройстве 2FA. Создайте новый файл validate.blade.php в папке resources/views/2fa и поместите в него следующий код представления:

 @extends('layouts.app') @section('content') <div class="container spark-screen"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">2FA</div> <div class="panel-body"> <form class="form-horizontal" role="form" method="POST" action="/2fa/validate"> {!! csrf_field() !!} <div class="form-group{{ $errors->has('totp') ? ' has-error' : '' }}"> <label class="col-md-4 control-label">One-Time Password</label> <div class="col-md-6"> <input type="number" class="form-control" name="totp"> @if ($errors->has('totp')) <span class="help-block"> <strong>{{ $errors->first('totp') }}</strong> </span> @endif </div> </div> <div class="form-group"> <div class="col-md-6 col-md-offset-4"> <button type="submit" class="btn btn-primary"> <i class="fa fa-btn fa-mobile"></i>Validate </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection 

Тестирование это

Теперь у нас должны быть все части, чтобы пример работал.

Вы можете использовать любую RFC 6238-совместимую программу, но чтобы следовать этому руководству, используйте Google Authenticator. Сначала перейдите в Apple iTunes App Store или Google Play Store и загрузите приложение.

Затем откройте целевую страницу приложения на http://homestead.app . Вы должны увидеть страницу приветствия. В верхнем правом углу есть ссылка для регистрации. Нажмите на нее и зарегистрируйте новую учетную запись.

Скриншот целевой страницы

Скриншот страницы регистрации

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

Скриншот включения 2FA на домашней странице

На экране вы должны увидеть QR-код. Это секретный ключ, созданный приложением, который необходимо загрузить в мобильное приложение 2FA.

Скриншот страницы включения 2FA

Откройте приложение Google Authenticator и нажмите кнопку «Добавить» в нижней части экрана. Следует спросить, хотите ли вы отсканировать штрих-код или вручную ввести секретный ключ. Выберите первый вариант.

Снимок экрана с заставкой Google Authenticator

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

Затем выйдите из веб-приложения и войдите снова. На этот раз, после того, как вы используете свой адрес электронной почты и пароль, вы должны увидеть экран, который запрашивает одноразовый пароль.

Убедитесь, что приложение Google Authenticator открыто, введите номер, который в данный момент отображается на веб-странице, и введите значение.

Снимок экрана главного экрана Google Authenticator

Скриншот страницы OTP

После этого вы должны пройти аутентификацию и перейти на домашнюю страницу.

Если вы хотите отключить 2FA, вы можете сделать это на домашней странице, нажав «Отключить 2FA» внизу.

Снимок экрана домашней страницы

Там будет страница подтверждения о том, что 2FA был отключен.

Снимок экрана отключенной страницы 2FA

Теперь вы можете войти без одноразового пароля.

Вывод

По умолчанию процесс входа в систему и процесс настройки TOTP не происходит через HTTPS. В производственной среде убедитесь, что это происходит по HTTPS.

В этой статье мы увидели, как добавление одноразового пароля в процесс аутентификации делает вход в систему более безопасным. Затем мы пошли через создание приложения в Laravel, которое использует реализацию Google Authenticator для выполнения 2FA.

Если вы хотите увидеть законченный пример, он находится на Github .