Инстант "по взрослому". Часть 1. Авторизация. Счетчик неверных входов. v1.10

+22
1.61K
Доброго всем дня!

Продолжим наши уроки по усовершенствованию Инстанта. Сегодня мы будем изучать и усовершенствовать первую ветку Инстанта. Вторую пока трогать не будем. И будем делать все по взрослому 😊
Итак, как я уже писал для многоуважаемого Fuze, одним из неудобств (и даже проблем) первого Инстанта является её система авторизации. Как вы заметили несколько версий назад появилась следующая фича — если вы ошибаетесь со вводом пароля, на следующем разе CMS предлагает вам ввести капчу. Этого сделано для того, чтобы хакер(скорее крякер) не "крякнул" пароль пользователя CMS простым перебором.
Вот об этом и поговорим на этом и следующем уроке.



Итак, я хочу (да и много где пишут об этом различные уважаемые и вежливые человеки) сделать следующую вещь — чтобы капча появилась только на третий неправильный ввод пароля. Мне лично очень не нравится писать капчу, когда я ошибся паролем. "Хакать" систему мы не будем, мы сделаем плагин, который это все и будет делать.
Итак, что нам нужно
1) увеличить на 1 счетчик неверных вводов если пароль неверный
2) если счетчик больше указанного числа (это кто-то подбирает, точно, точно) — вывести капчу
3) если пароль введен верно — сбросить счетчик

В Инстанте 1.10.x появилась замечательная возможность заменить экшен (action) компонента или выполнить какие-то действия перед этим экшеном. Вот этим мы у будем пользоваться. Взяв как пример встроенный плагин P_DEMO_ROUTE и посмотрев как работает вызов экшена в ядре приходим к следующему выводу.
Наш плагин, пусть он называется p_authcnt будет обрабатывать всего два события:

USER_LOGIN — который вызывается после успешной авторизации
GET_REGISTRATION_ACTION_AUTH — экшен AUTH компонента REGISTRATION — как раз он обрабатывает вход и перед его вызовом мы и будем воплощать свои фантазии. Заменять нам его не нужно, нам только нужно направить его работу в нужное нам русло.

Итак добавим в наш плагин строки

$this->events[] = 'GET_REGISTRATION_ACTION_AUTH';
$this->events[] = 'USER_LOGIN';

и конфиг по умолчанию:

$this->config['P_BADAUTHCOUNT'] = 3;

это три неверных ввода

Далее при установке плагина добавим поле в таблицу cms_users для подсчета входов
это кусочек из метода install()

  1.  
  2.  
  3. if (!$inDB->isFieldExists('cms_users', 'authcnt')){
  4.  
  5. $inDB->query("ALTER TABLE `cms_users` ADD `authcnt` INT DEFAULT 0");
  6.  
Так начнем с простого чтобы нам сбросить счетчик при верном входе напишем обработчик события USER_LOGIN


  1.  
  2. private function eventUserLogin($user) {
  3.  
  4. $inCore = cmsCore::getInstance();
  5. $inDB = cmsDatabase::getInstance();
  6.  
  7. $inDB->query("UPDATE cms_users SET authcnt = 0 WHERE id = '{$user['id']}'");
  8. }
  9.  
И сразу приведу код метода события GET_REGISTRATION_ACTION_AUTH

  1. private function eventAuth() {
  2.  
  3. if( !cmsCore::inRequest('logout') ) {
  4.  
  5. $inCore = cmsCore::getInstance();
  6. $inDB = cmsDatabase::getInstance();
  7.  
  8. $antibruteforce = cmsUser::sessionGet('anti_brute_force');
  9.  
  10. $login = cmsCore::request('login', 'str', '');
  11. $passw = cmsCore::request('pass', 'str', '');
  12.  
  13. if ($login=="") {
  14. $login = cmsUser::sessionGet('login','user');
  15.  
  16. }
  17.  
  18. // логин или почта?
  19. if (!$login || !$passw){
  20. if (!preg_match("/^([a-zA-Z0-9\._-]+)@([a-zA-Z0-9\._-]+)\.([a-zA-Z]{2,4})$/ui", $login)){
  21. $where_login = "login = '{$login}'";
  22. } else {
  23. $where_login = "email = '{$login}'";
  24. }
  25.  
  26. if ($antibruteforce == 1) {
  27. $antibruteforce = 0;
  28. $inDB->query('UPDATE cms_users SET authcnt = authcnt + 1 WHERE '.$where_login);
  29. cmsUser::sessionDel('anti_brute_force');
  30. }
  31.  
  32. $cnt = $inDB->get_field('cms_users',$where_login,'authcnt');
  33. echo("Количество неверных вводов пароля:".$cnt."
  34. ");
  35. if ($cnt >= $this->config['P_BADAUTHCOUNT']) {
  36. $antibruteforce = 1;
  37. cmsUser::sessionPut('anti_brute_force', 1);
  38. }
  39. }
  40. }
  41. return false;
  42.  
  43. }
  44.  
Метод работает очень просто — он получает признак вывода капчи $antibruteforce, если он равен 1 — значит был неверный вход (задачка на дом — а где он ставится в единичку?), выключает его временно, увеличивает на 1 счетчик и тут же проверяет, если он стал больше указанного нами в конфиге числа входов — включает признак обратно (чтобы капча все-таки вылезла)
И отдает управление далее "старому" экшену AUTH.
Вот и все
Что мы хотели то и в принципе получили. НО, все также плохо и неудобно. Признак капчи лежит в сессии, если хакер будет постоянно удалять куку сессии — смысла от нашей капчи нет.
Я сделал следующую версию плагина, которая работает на ajax и выводит капчу только на N-ный неверный ввод без перезагрузки страницы. В чем разница спросите вы — а разница в том, что от сессии уже ничего не зависит и вот тут хакер уже и не отвертится от ввода капчи.
Но об этом на следующем уроке.

Полный текст плагина разобранного на этом уроке

  1. /******************************************************************************/
  2. // //
  3. // Auth Counter and Protector //
  4. // //
  5. // //
  6. // based on InstantCMS 2007-2014 //
  7. // produced by Krot, //
  8. // //
  9. // free license //
  10. // //
  11. /******************************************************************************/
  12.  
  13. class p_authcnt extends cmsPlugin {
  14.  
  15. // ==================================================================== //
  16.  
  17. public function __construct(){
  18.  
  19. parent::__construct();
  20.  
  21. // Информация о плагине
  22. $this->info['plugin'] = 'p_authcnt';
  23. $this->info['title'] = 'Auth Counter';
  24. $this->info['description'] = 'Подсчет неверных авторизаций';
  25. $this->info['author'] = 'krot';
  26. $this->info['version'] = '1.10';
  27.  
  28. // События, которые будут отлавливаться плагином
  29. // $this->events[] = 'GET_ROUTE_REGISTRATION'; // это нам пока не надо
  30. $this->events[] = 'GET_REGISTRATION_ACTION_AUTH';
  31. $this->events[] = 'USER_LOGIN';
  32.  
  33. $this->config['P_BADAUTHCOUNT'] = 3;
  34. }
  35.  
  36. // ==================================================================== //
  37.  
  38. /**
  39.   * Процедура установки плагина
  40.   * @return bool
  41.   */
  42. public function install(){
  43.  
  44. $inDB = cmsDatabase::getInstance();
  45.  
  46. if (!$inDB->isFieldExists('cms_users', 'authcnt')){
  47.  
  48. $inDB->query("ALTER TABLE `cms_users` ADD `authcnt` INT DEFAULT 0");
  49.  
  50. }
  51.  
  52. return parent::install();
  53.  
  54. }
  55.  
  56. // ==================================================================== //
  57.  
  58. /**
  59.   * Процедура обновления плагина
  60.   * @return bool
  61.   */
  62. public function upgrade(){
  63.  
  64. return parent::upgrade();
  65.  
  66. }
  67.  
  68. // ==================================================================== //
  69.  
  70. /**
  71.   * Обработка событий
  72.   * @param string $event
  73.   * @param mixed $data
  74.   * @return mixed
  75.   */
  76. public function execute($event='', $data=array()){
  77.  
  78. parent::execute();
  79.  
  80. switch ($event){
  81. // case 'GET_ROUTE_USERS': $data = $this->eventGetRoutes($data); break; // это пока не надо
  82. case 'GET_REGISTRATION_ACTION_AUTH': $data = $this->eventAuth(); break;
  83. case 'USER_LOGIN': $this->eventUserLogin($data); break;
  84. }
  85.  
  86. return $data;
  87.  
  88. }
  89.  
  90. // ==================================================================== //
  91.  
  92. private function eventGetRoutes($routes) {
  93.  
  94. // формируем массив по аналогии с router.php
  95. $add_routes[] = array(
  96. '_uri' => '/^registration\/login$/i',
  97. 'do' => 'auth'
  98. );
  99.  
  100. // перебираем массив $add_routes, занося каждый в начало входного массива $routes
  101. foreach($add_routes as $route){
  102. array_unshift($routes, $route);
  103. }
  104.  
  105. return $routes;
  106.  
  107. }
  108.  
  109. // ==================================================================== //
  110.  
  111. private function eventAuth() {
  112.  
  113. if( !cmsCore::inRequest('logout') ) {
  114.  
  115. $inCore = cmsCore::getInstance();
  116. $inDB = cmsDatabase::getInstance();
  117.  
  118. $antibruteforce = cmsUser::sessionGet('anti_brute_force');
  119.  
  120. // это тоже вроде не надо
  121. $login = cmsCore::request('login', 'str', '');
  122. $passw = cmsCore::request('pass', 'str', '');
  123.  
  124. if ($login=="") {
  125. $login = cmsUser::sessionGet('login','user');
  126.  
  127. }
  128.  
  129. // если нет логина или пароля, показываем форму входа
  130. if (!$login || !$passw){
  131. if (!preg_match("/^([a-zA-Z0-9\._-]+)@([a-zA-Z0-9\._-]+)\.([a-zA-Z]{2,4})$/ui", $login)){
  132. $where_login = "login = '{$login}'";
  133. } else {
  134. $where_login = "email = '{$login}'";
  135. }
  136.  
  137. if ($antibruteforce == 1) {
  138. $antibruteforce = 0;
  139. $inDB->query('UPDATE cms_users SET authcnt = authcnt + 1 WHERE '.$where_login);
  140. cmsUser::sessionDel('anti_brute_force');
  141. }
  142.  
  143. $cnt = $inDB->get_field('cms_users',$where_login,'authcnt');
  144. echo("Количество неверных вводов пароля:".$cnt."");
  145. if ($cnt >= $this->config['P_BADAUTHCOUNT']) {
  146. $antibruteforce = 1;
  147. cmsUser::sessionPut('anti_brute_force', 1);
  148. }
  149. }
  150. }
  151. return false;
  152.  
  153. }
  154.  
  155. private function eventUserLogin($user) {
  156.  
  157. $inCore = cmsCore::getInstance();
  158. $inDB = cmsDatabase::getInstance();
  159.  
  160. $inDB->query("UPDATE cms_users SET authcnt = 0 WHERE id = '{$user['id']}'");
  161.  
  162. }
  163.  
  164. // ==================================================================== //
  165.  
  166. }
скачать тут

делалось на 1.10.3

P.S. далее может быть (все зависит от вашего желания)
— авторизация ajax с подсчетом неверных входов и выводом капчи по необходимости (возможность использования в окне)
— защита от подбора увеличением времени ввода пароля
— учет неверных вводов для каждой сессии и общего количества сессий с подбором в опред.промежуток времени и защита от этого

— регистрация ajax (тут вот в предыдущем посте и окошко сделали 😊)
— прикрутка ко всему этому регистрации через соцсети на своем сайте и через сайт поставщик

Демо demo1.instantdemo.ru

Юзеры:

fedor
vasya

пароли 1234, 12345678 или 123456 — сами подберите 😊
+3
Крот Крот 7 лет назад #
Уважаемые админы - если я оборачиваю полный код плагина в <CODE - /CODE> - инстант ломается тут
поправьте пжл.
0
lokanaft lokanaft 7 лет назад #
Слишком много символов становится и пост обрезает
0
Raiden Raiden 7 лет назад #
Спасибо, довольно подробно разжёвано)
""
0
Александр Александр 7 лет назад #
Круто, а он будет работать с новым плагинов Fuz'a который ведет логи авторизаций?
+1
Крот Крот 7 лет назад #
Да спасибо, а почему бы и нет?

P.S. честно, я не проверял на совместимость
0
Александр Александр 7 лет назад #
Как думаете, а на 1.0.4 в последствии он пойдет или нет? а так за плагин +++
0
Крот Крот 7 лет назад #
да, должен, вот следующий плагин - будет ругаться но не смертельно...

Еще от автора

Инвайтер 1.9 для 1.10.6
Доброго всем времени суток! Решил нарушить сложившуюся здесь традицию и решил вернуться сюда с обновлениями.
Инстант "по взрослому". Часть 2. Авторизация. Аякс. v1.10
Доброго всем времени суток! Продолжая серию Инстант "по взрослому", сегодня хотел бы показать более мощный плагин авторизации.
Отложенная публикация статьи. Instant 2.01
Доброго всем времени суток!
Используя этот сайт, вы соглашаетесь с тем, что мы используем файлы cookie.