[spoiler]
Одноразовые пароли
При включении двухэтапной авторизации пользователю требуется пройти два этапа для авторизации на сайте. На первом этапе необходимо указать свой логин и пароль. На втором - ввести одноразовый код, который можно получить с помощью специального брелока или мобильного телефона.
Напомню, что ранее поддерживались одноразовые пароли по счетчику, которые нужно было водить в форме логина в поле пароля. Поддерживались аппаратные брелоки для генерирования таких ОП. С тех пор одноразовые пароли стали популярнее, появились мобильные приложения для генерирования ОП на телефоне. В 15 версии мы поддерживаем уже два вида ОП:
- по счетчику (HMAC-Based One-time Password, HOTP), описан в
- по времени (Time-based One-time Password, TOTP), описан
Была решена задача поддержки популярных мобильных приложений для обоих типов ОП. Общепринятым способом инициализации ОП является сканирование и распознавание QR-кода телефоном. Это избавляет пользователя от ввода длинных ключей (хотя мы оставили возможность и ручной инициализации). Мы выпустили свое мобильное приложение Bitrix OTP для Android и iOS. Кроме того, поддерживаются другие приложения (Google Authenticator, FreeOTP).
Вот так выглядит инициализация в панели управления:
Для публичной части создан компонент security.user.otp.init, который так же выводит QR-код:
После инициализации пользователю предлагается создать резервные коды для авторизации на случай, если утеряно устройство генерирования ОП. Использование резервных кодов включается в настройках модуля security. Это очень важно! Если отсутствует доступ к скриптам сервера, то потеря устройства или сбой мобильного приложения у администратора приводит к полной невозможности авторизоваться! Спасут только резервные коды, сохраненные в надежном месте (компонент security.user.recovery.codes):
Давайте теперь немного углубимся в разработку формы авторизации на сайте или КП. Для удобства пользователей мы сделали разделение форм ввода основного пароля и ОП. Теперь одноразовый код спрашивается отдельным шагом авторизации:
(На Bitrix24 форма красивее)
Форма выводится системным компонентом system.auth.otp. Вы можете кастомизировать шаблон этого компонента для своего сайта точно так же, как шаблоны других компонентов авторизации. Также доработан компонент system.auth.form, его шаблон по умолчанию теперь понимает ОП. Если на вашем сайте используется кастомизированный шаблон system.auth.form, не переживайте, по-прежнему поддерживается ввод ОП в поле основного пароля. Если вы хотите сделать ввод ОП отдельным шагом, то нужно выполнить следующую доработку в шаблоне:
<? elseif($arResult["FORM_TYPE"] == "otp"): ?> <form name="system_auth_form<?=$arResult["RND"]?>" method="post" target="_top" action="<?=$arResult["AUTH_URL"]?>"> <?if($arResult["BACKURL"] <> ''):?> <input type="hidden" name="backurl" value="<?=$arResult["BACKURL"]?>" /> <?endif?> <input type="hidden" name="AUTH_FORM" value="Y" /> <input type="hidden" name="TYPE" value="OTP" /> <table width="95%"> <tr> <td colspan="2"> <?echo GetMessage("auth_form_comp_otp")?><br /> <input type="text" name="USER_OTP" maxlength="50" value="" size="17" autocomplete="off" /></td> </tr> <?if ($arResult["CAPTCHA_CODE"]):?> <tr> <td colspan="2"> <?echo GetMessage("AUTH_CAPTCHA_PROMT")?>:<br /> <input type="hidden" name="captcha_sid" value="<?echo $arResult["CAPTCHA_CODE"]?>" /> <img src="/bitrix/tools/captcha.php?captcha_sid=<?echo $arResult["CAPTCHA_CODE"]?>" width="180" height="40" alt="CAPTCHA" /><br /><br /> <input type="text" name="captcha_word" maxlength="50" value="" /></td> </tr> <?endif?> <?if ($arResult["REMEMBER_OTP"] == "Y"):?> <tr> <td valign="top"><input type="checkbox" id="OTP_REMEMBER_frm" name="OTP_REMEMBER" value="Y" /></td> <td width="100%"><label for="OTP_REMEMBER_frm" title="<?echo GetMessage("auth_form_comp_otp_remember_title")?>"><?echo GetMessage("auth_form_comp_otp_remember")?></label></td> </tr> <?endif?> <tr> <td colspan="2"><input type="submit" name="Login" value="<?=GetMessage("AUTH_LOGIN_BUTTON")?>" /></td> </tr> <tr> <td colspan="2"><noindex><a href="<?=$arResult["AUTH_LOGIN_URL"]?>" rel="nofollow"><?echo GetMessage("auth_form_comp_auth")?></a></noindex><br /></td> </tr> </table> </form> |
Если вы разрабатываете свой компонент с авторизацией, то вам будет полезен пример из компонента system.auth.form. Работа идет с API модуля security:
if(CModule::IncludeModule("security") && Mfa\Otp::isOtpRequired() && $_REQUEST["login_form"] <> "yes") { $arResult["FORM_TYPE"] = "otp"; $arResult["REMEMBER_OTP"] = (COption::GetOptionString('security', 'otp_allow_remember') === 'Y'); $arResult["CAPTCHA_CODE"] = false; if(Mfa\Otp::isCaptchaRequired()) { $arResult["CAPTCHA_CODE"] = $APPLICATION->CaptchaGetCode(); } if(Mfa\Otp::isOtpRequiredByMandatory()) { $arResult['ERROR_MESSAGE'] = array("MESSAGE" => GetMessage("system_auth_form_otp_required"), "TYPE" => "ERROR"); } } |
elseif($_REQUEST["TYPE"] == "OTP") { $arAuthResult = $GLOBALS["USER"]->LoginByOtp($_REQUEST["USER_OTP"], $_REQUEST["OTP_REMEMBER"], $_REQUEST["captcha_word"], $_REQUEST["captcha_sid"]); } |
- требовать обязательности ОП у пользователей - с выбором категорий пользователей и указанием периода отсрочки включения ОП;
- временно отключать ОП у пользователя - если он забыл брелок дома.
Отключение на срок.
Настройки двух-этапной авторизации модуля security.
Пароли приложений
Если вы помните, в начале я писал, что ОП добавлены в КП. Может возникнуть резонный вопрос: а почему собственно только сейчас одноразовые пароли появились в КП, они же давно уже существуют? Дело в том, что в корпоративном портале много различных интеграций с внешними приложениями, например Outlook, WebDAV или календари по CalDAV. С практической точки зрения эти приложения невозможно использовать при включенных ОП, т.к. они бы запрашивали пароль при каждом обращении к серверу.
Для поддержки таких интеграций мы создали новую сущность: пароль приложения (ПП). Это пароль, выдаваемый для конкретного приложения, и только оно с этим паролем может работать. Почему? Потому что считается, что безопасность работы без одноразового пароля снижена, поэтому доступ приложения к серверу сильно ограничен со стороны сервера. Например, мобильное приложение может обращаться только к папке /mobile, а десктопное - к папке /desktop_app. При попытке обратиться с выданным паролем за пределы отведенных папок ("скоупов" ) будет выдана ошибка "403 Forbidden". Таким образом поддерживается баланс между безопасностью и удобством.
За генерирование ПП отвечает новый компонент main.app.passwords:
Сгенерированный пароль один раз показывается на экране, его нужно скопировать или сразу записать. После закрытия окна пароль нельзя больше увидеть в открытом виде:
Обратите внимание, что после включения одноразовых паролей, для нашего мобильного и десктопного приложения не нужно генерировать ПП вручную. Они сами его создадут после однократного ввода ОП в приложении.
С точки зрения разработки, авторизация по ПП производится в той же функции CUser::Login(), как и по обычному паролю. Это сделано для совместимости, чтобы существующие скрипты понимали новую сущность ПП. Эта функция сначала пытается авторизовать пользователя по его обычному паролю, а если он не подошел, то по паролю приложения:
if(!$passwordCorrect) { //let's try to find application password if(($appPassword = ApplicationPasswordTable::findPassword($arUser["ID"], $arParams["PASSWORD"], ($arParams["PASSWORD_ORIGINAL"] == "Y"))) !== false) { $passwordCorrect = true; $applicationId = $appPassword["APPLICATION_ID"]; $applicationPassId = $appPassword["ID"]; } } |
В параметре APPLICATION_ID текущего пользователя запоминается, что авторизация произошла по ПП. Именно это позволяет отличить обычную авторизацию от авторизации по ПП и проконтролировать доступ приложения:
//application password scope control if(($applicationID = $GLOBALS["USER"]->GetParam("APPLICATION_ID")) !== null) { $appManager = \Bitrix\Main\Authentication\ApplicationManager::getInstance(); if($appManager->checkScope($applicationID) !== true) { CHTTP::SetStatus("403 Forbidden"); die(); } } |
Список приложений, для которых может быть сгенерирован ПП, не является "вшитым". Если на вашем сайте или КП есть интеграция, для которой не предусмотрен ПП, то вы можете описать собственное приложение с вашим контролем доступа.
1) Вы должны создать свой класс, отнаследовав его от базового:
namespace Bitrix\Main\Authentication; use Bitrix\Main; class Application { protected $validUrls = array(); public function __construct() { } /** * Checks the valid scope for the applicaton. * * @return bool */ public function checkScope() { /** @var Main\HttpRequest $request */ $request = Main\Context::getCurrent()->getRequest(); $realPath = $request->getScriptFile(); foreach($this->validUrls as $url) { if(strpos($realPath, $url) === 0) { return true; } } return false; } } |
Базовый класс содержит метод проверки доступа, вы его можете переопределить. Для самого простого случая проверки доступа по путю вы можете переопределить только свойство $validUrls, содержащее список путей:
class DesktopApplication extends Bitrix\Main\Authentication\Application { protected $validUrls = array( "/desktop_app/", "/bitrix/tools/disk/", "/bitrix/services/disk/index.php" ); public static function OnApplicationsBuildList() { return array( "ID" => "desktop", "NAME" => GetMessage('DESKTOP_APPLICATION_NAME'), "DESCRIPTION" => GetMessage("DESKTOP_APPLICATION_DESC"), "SORT" => 2100, "CLASS" => "DesktopApplication", ); } } |
2) Вы должны подписаться на событие ядра OnApplicationsBuildList:
RegisterModuleDependences("main", "OnApplicationsBuildList", "im", "DesktopApplication", "OnApplicationsBuildList"); |
Обработчик события должен вернуть массив (см. пример выше), описывающий приложение.
Таким образом, вы можете добавлять свои приложения, чтобы избежать ввода одноразового пароля. При этом обычная авторизация пользователя в браузере будет защищена ОП.