Модераторы: skyboy, MoLeX, Aliance, ksnk
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Практикум. Пишем систему Регистрации/Авторизации 
:(
    Опции темы
Mal Hack
Дата 23.5.2005, 23:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Мудрый...
****


Профиль
Группа: Участник Клуба
Сообщений: 9926
Регистрация: 15.2.2004

Репутация: 122
Всего: 261



В принципе реализация самой авторизации и регистрации пользователей не является проблемой. Вся проблема заключается в безопасности, тобишь, мы должны отследить следующие моменты, дабы
  •  Нельзя было бы быть авторизованным под разными IP.
  •  Нельзя было бы быть авторизованным под разными Браузерами.
  •  Ограничить время жизни сессии пользователя 10-15 минутами ПРОСТОЯ !
Все это делается для того, чтобы снизить вероятность авторизации, точнее ее подмены, с разных компьютеров.

Рассмотрим мы с вами суть проблемы в следующем порядке:

1. Технология обеспечения безопасности и алгоритм действий.
2. Создаем таблицы в БД (MySQL)
3. Подготовка.
4. Регистрация.
5. Авторизация.
6. Продление сессии, при каждом новом действии пользователя. 
7. Куки или сессии?
8. Что дальше? Что можно усложнить?

-------------------------------------------------------------------

1. Технология обеспечения безопасности и алгоритм действий.

Итак, начнем по порядку. Первое что мы должны сделать, это снизить вероятность подмены данных и получения авторизированного доступа. К примеру. Пользователь логинится и дальше авторизация поверяется лишь наличием Куки с именем auth. Этот вариант совершенно неправильный, поскольку ничего не составляет передать скрипту эту самую куку с другого компьютера. 

Сама же подмена может осуществляться через непосредственную посылку данных скрипту через заголовки. Новичкам это мало что говорит, но те кто более менее знаком с сокетами и header(); поймут о чем я.

Дабы не разводить демагогию текстом сразу опишу по каким параметрам и как мы будем строить защиту.
1. При авторизации мы будем искать в таблице с пользователями запись, де имя идентично тому, которое ввел пользователь, а MD5 хэш от пароля введенного пользователем равен хэшу, записанному в таблицу при регистрации пользователя.
2. При успешной авторизации пользователю пишутся куки, с его именем (“name”) и уникальным временем авторизации (“time”). Пусть вас не смущает использование времени, если его взять с дробными частями секунды, то уж никак не получится, чтобы два пользователя залогинились в одно и тоже время. Это – невозможно технически. 
3. Мы будем хранить в базе в таблице сессий уникальный хэш (шифр), который будем составлять из вышеупомянутых кук, кода браузера пользователя и IP адреса. Правда, «name» мы использовать не будем, по нему мы будем узнавать ID пользователя, который уже и пойдет в хэш безопасности. Затем по нему и проверять, залогинен ли пользователь или нет, т.е. активна ли его сессия или нет.

Я всегда считал, считаю, и буду считать, что самое важное при программировании это правильно поставить задачу и продумать алгоритм, т.е. последовательность действий.
Наша последовательность действий такая.
1. Если нажата кнопка с именем blogin, мы должны запустить функцию авторизации.
2. Если нажата кнопка breg, мы должны запустить функции регистрации пользователя и автоматической авторизации после этого.
3. Если у пользователя есть установленные нами куки, мы должны проверить, а есть ли у этого пользователя активная сессия. Если да, то мы будем считать этого пользователя авторизованным.

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

Для совсем новичков, опишу функции, которые мы беем использовать. По сути – это структурная схема программы (скрипта), которая просто необходима при разработки больших приложений, дабы не держать в памяти, что и какая функция у нас будет делать.
  • get_ip(); - возвращает IP пользователя
  • hash (); - вычисляет хэш безопасности и возвращает его. Получает в качестве параметров: «ID пользователя», «Браузер пользователя», «IP адрес пользователя», «уникальное время авторизации».
  • getmicrotime(); - возвращает время с дробными частями секунды (нужно для получения уникального времени регистрации).
  • redirect(); - делает редирект с параметром, по значению которого выводится или не выводится сообщение об ошибке. В качестве параметра получает внутренний код ошибки в скрипте.
  • auth(); - функция производит действия по авторизации пользователя.
  • reg(); - функция производит действия по регистрации пользователя.
  • check(); - функция проверяет, авторизован ли пользователь или нет. К тому же, если авторизованный пользователь нажал по ссылке «выход», уничтожает активную сессию пользователя.
2. Создаем таблицы в БД (MySQL) 

Нам понадобится 2 таблицы. Первая будет хранить информацию о пользователях, а вторая – о самих сессиях, авторизованных пользователей.
Код
CREATE TABLE `auth_members` (
`id` SMALLINT NOT NULL AUTO_INCREMENT ,
`name` TINYTEXT NOT NULL ,
`password` TINYTEXT NOT NULL ,
PRIMARY KEY ( `id` ) 
);

CREATE TABLE `auth_sessions` (
`member` SMALLINT NOT NULL ,
`time` INT( 14 ) NOT NULL ,
`hash` TINYTEXT NOT NULL ,
);


3. Подготовка

Тут я опишу функции и код, которой необходимы для авторизации и регистрации..
Все будем пихать в один файл, дабы для примера кода не много. Пусть он у нас называется qwe.php.
Напишем функцию для создания хэша безопасности:
Код
<?php

 function hash( $user , $user_agent , $user_ip )
  {  return md5( $user ) . md5( $user_agent ) . md5( $user_ip );  }

?>

Теперь для определения IP пользователя:
Код
<?php
 function get_ip()
  {
   if ($ip = getenv("HTTP_CLIENT_IP"))
    {  return $ip;  }

   if ($ip = getenv("HTTP_X_FORWARDED_FOR"))
    {
     if ($ip == '' || $ip == "unknown")
      {  $ip = getenv("REMOTE_ADDR");  }

     return $ip;
    }
   if ( $ip = getenv("REMOTE_ADDR") )
    {  return $ip;  }
  } 
?>

Для определения текущего времени с точностью в миллисекундах:
Код
<?php

function getmicrotime()
  {
   $mt = explode( " ", microtime() );
   return ( (float)$mt[0] + (float)$mt[1] );
  }

?>

Еще нам понадобится функция, которая будет делать редирект на этот же скрипт, с передачей параметра с возникшей ошибкой при регистрации или авторизации. Именно редирект нужен для того, чтобы очистить POST данные:
Код
<?php

function redirect( $par )
  {
   header( "Location: qwe.php?task=" . $par );
   exit;
  }

?>

Теперь сделаем так, что в случае если пользователь авторизован, ему покажется сообщение «Welcome»и ссылка для выхода ( qwe.php?quit ), в противном случае форма ввода логина и пароля. В конец скрипта пишем:
Код
<?php

 if( isset( $_COOKIE['time'] ) && isset( $_COOKIE['name'] ) && check() == TRUE )
  {  print "Welcome.<br><a href='?quit'>Выйти</a>";  }
 else
  {
   if( ! empty( $_GET['task'] ) )
    {
     switch( $_GET['task'] )
      {
       case "nepass":   print "Пароли не совпадают";
            break;
       case "not_auth": print "Логин или пароль неверный.";
            break;
       case "fields":   print "Не заполнены все поля";
            break;
       case "exists":   print "Пользователь с таким именем уже существует";
            break;
      }

     print "<br><br>";
    }

?>
Вход: <form action='' method='POST'>
Логин: <input type='text' name='name' maxlength='127' value=''><br>
Пароль: <input type='text' name='pass' maxlength='127' value=''><br>
<input type='Submit' name='blogin' value='Вход'><br>
</form>
Регистрация: <form action='' method='POST'>
Логин: <input type='text' name='name' maxlength='127' value=''><br>
Пароль: <input type='text' name='pass1' maxlength='127' value=''><br>
Повторите пароль: <input type='text' name='pass2' maxlength='127' value=''><br>
<input type='Submit' name='breg' value='Вход'>
</form>
<?php
  }

?>

В первой строчке мы делаем проверку. Если у пользователя есть две куки и функция check(); которая будет проверять, залогинен ли пользователь (если да вернет TRUE, т.е. «истина» или FALSE, т.е. «ложь» в противном случае). Иначе мы показываем форум с авторизацией и регистрацией перед этим, проверяя, если пришел не пустой параметр task (как мы помним он передается из функции redirect), то выводим соответствующую ошибку. 

4. Регистрация

Итак. Первое – мы пишем код:
Код
<?php

 if( $_SERVER['REQUEST_METHOD'] == "POST" && isset( $_POST['breg'] ) )
  {
   if( empty( $_POST['name'] ) || empty( $_POST['pass1'] ) || empty( $_POST['pass2'] ) )
    {  redirect( "fields" );  }

   if( md5( $_POST['pass1'] ) != md5( $_POST['pass2'] ) )
    {  redirect( "nepass" );  }

   reg();
  }

?>

Первым делом проверяем: «Если нажата кнопка с именем blogin». Проверку на метод, по которому идет обращение к скрипту можно в принципе опустить, но иногда это бывает полезно, поэтому лучше все же писать условие: $_SERVER['REQUEST_METHOD'] == "POST".

Затем мы осуществляем проверку на заполнение всех полей формы. Если хотя бы одно поле не заполнено, мы делаем редирект с параметров “fields”, при котором у нас будет показана ошибка: «Не заполнены все поля».

После этого делаем проверку на несоответствие введенных паролей. Делаем редирект, если пароли не совпадают.

Теперь, если у нас не произошел редирект, мы перейдем к выполнению функции регистрации:
Код
<?php

function reg()
  {
   $result = mysql_query( "SELECT `*` FROM `auth_members` WHERE `name` = '" . mysql_escape_string( $_POST['name'] ) . "' LIMIT 1" );

   if( mysql_num_rows( $result ) == 1 )
    {  redirect( "exists" );  }

   mysql_query( "INSERT INTO `auth_members` VALUES ( '' , '" . mysql_escape_string( $_POST['name'] ) . "' , '" . md5( $_POST['pass1'] ) . "' )" );
   $_POST['pass'] = $_POST['pass1'];

   auth();
  }

?>

Первым делом, мы посылаем запрос на поиск пользователя с именем, которое указано в форме регистрации. Если пользователь с таким именем будет найден, то в скрипт вернется 1 запись, что мы дальше и проверяем. Соответственно при выполнении условия делаем редирект с параметром «exists» для показа ошибки: «Пользователь с таким именем уже существует».

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

После чего переходим к функции авторизации пользователя.

Строчка $_POST['pass'] = $_POST['pass1']; пишется лишь для того, чтобы не передавать параметры в auth(), которая использует $_POST['pass'] в качестве переданного с формы пароля, но при регистрации поля pass у нас нет. Это несколько неправильно, поскольку передача параметров облегчает отладку и работу с кодом. Но для того чтобы перед глазами начинающих веб-программистах не было огромного скопления переменных, я сознательно пошел на этот шаг.

5. Авторизация

Теперь можно приступить к авторизации.
Код
<?php

 if( $_SERVER['REQUEST_METHOD'] == "POST" && isset( $_POST['blogin'] ) )
  {
   if( empty( $_POST['name'] ) || empty( $_POST['pass'] ) )
    {  redirect( "fields" );  }

   auth();
  }

?>

Опять же идет проверка не метод, которым запрашивается наш скрипт и имя кнопки «blogin». При выполнении этого условия, проверяем поля логина и пароля на то, что они не пустые. Если они пусты – делаем редирект. В противном случае переходим к функции auth();.
Код
<?php

function auth()
  {
   $result = mysql_query( "SELECT `*` FROM `auth_members` WHERE `name` = '" . mysql_escape_string( $_POST['name'] ) . "' AND `password` = '" . md5( $_POST['pass'] ) . "' LIMIT 1" );

   if( mysql_num_rows( $result ) == 0 )
    {  redirect( "not_auth" );  }

   $user = mysql_fetch_object( $result );

   $tm = getmicrotime();
   mysql_query( "INSERT INTO `auth_sessions` VALUES( " . $user -> id . " , " . time() . " , '" . hash( $user -> name , $_SERVER['HTTP_USER_AGENT'] , get_ip() , $tm ) . "' )" );

   setcookie( "time" , $tm , time() + 900 , "/" );
   setcookie( "name" , $user -> name , time() + 900 , "/" );

   redirect( "" );
  }

?>

Первым делом мы проверяем, а есть ли такой пользователь, и совпадает ли MD5 хэш от введенного пароля в форме с тем, который находится в Базе Данных. Если совпадает, то нам вернется запись с информацией о пользователе. Если вернется 0 записей, значит такого пользователя нет или пароль неверный, вследствие чего мы делаем редирект.

Теперь, когда мы уже знаем, что пользователь ввел в форме правильный логин и пароль, нам надо создать в базе данных сессию для этого пользователя. Но предварительно мы должны узнать ID данного пользователя. Для этого функцией mysql_fetch_object(); создаем объект, свойствами которого будут поля таблицы auth_members. В $user -> id будет храниться нужный нам ID пользователя. 

Строчкой $tm = getmicrotime(); мы получаем в переменную – уникальное текущее время Дальше запросом вставляем в таблицу сессий новую запись, указывав ID пользователя, текущее время time и сводный хэш безопасности о котором писалось раньше.

После этого устанавливаем пользователю в куки две переменных. Первая – уникальное время, в которое пользователь авторизовался, вторая – имя пользователя. Время жизни кук устанавливаем на 15 минут. После чего делаем редирект с пустым параметром, т.е. номинально, для того чтобы стереть POST данные.

6. Продление сессии, при каждом новом действии пользователя. 

Этим вопросом у нас будет заниматься функция check();
Код
<?php

function check()
  {
   $result = mysql_query( "SELECT `*` FROM `auth_members` WHERE `name` = '" . mysql_escape_string( $_COOKIE['name'] ) . "' LIMIT 1" );

   if( mysql_num_rows( $result ) == 0 )
    {  redirect( "not_auth" );  }

   $user = mysql_fetch_object( $result );

   $minutes = 15;
   $result = mysql_query( "SELECT `*` FROM `auth_sessions` WHERE `member` = " . $user -> id . " AND `hash` = '" . hash( $user -> name , $_SERVER['HTTP_USER_AGENT'] , get_ip() , $_COOKIE['time'] ) . "' AND `time` > " . ( time() - ( $minutes * 60 ) ) . " LIMIT 1 " );
   print mysql_error();

   if( mysql_num_rows( $result ) == 0 )
    {  redirect( "not_auth" );  }

   if( isset( $_GET['quit'] ) )
    {
     mysql_query( "DELETE FROM `auth_session` WHERE `id` = " . $user -> id . " AND `hash` = '" . hash( $user -> name , $_SERVER['HTTP_USER_AGENT'] , get_ip() , $_COOKIE['time'] ) . "'" );

     setcookie( "time" , "" , 900 , "/" );
     setcookie( "name" , "" , 900 , "/" );

     return FALSE;
    }
   else
    {
     mysql_query( "UPDATE `auth_session` SET `time` = " . time() . " WHERE `member` = " . $user -> id . " AND `hash` = '" . hash( $user -> name , $_SERVER['HTTP_USER_AGENT'] , get_ip() , $_COOKIE['time'] ) . "'" );

     setcookie( "time" , $_COOKIE['time'] , time() + ( $minutes * 60 ) , "/" );
     setcookie( "name" , $_COOKIE['id'] , time() + ( $minutes * 60 ) , "/" );

     return TRUE;
    }
  }

?>

В принципе все, что в ней написано мы уже использовали в функциях авторизации и регистрации. 

Первые строчки служат для проверки существования пользователя и получения его ID.

Затем идет то, что мы еще не делали. Мы посылаем запрос к таблице с сессиями, где ищем, есть ли сессия для пользователя с этим ID, хэшэм безопасности и была ли она активна в течение последних 15 минут (переменная $minutes). Если нет, т.е. запрос вернет 0 строк, то мы перекинем пользователя на форму регистрации и авторизации. Очищать куки в данном случае не обязательно, поскольку, как оказалось, они все равно не позволяют пользователю залогиниться.

Дальше если пользователь выходит из авторизированной зоны (нажимает ссылку ?quit), то мы удаляем запись из таблицы сессий, ту, ища ее по ID пользователя и хэшу безопасности. После чего пишем в куки пустые значения и ставим время жизни далеко в прошлом. Затем выходим из функции и возвращаем FALSE, чтобы не выполнился код, описанный в третьей части статьи:
Код
<?php

if( isset( $_COOKIE['time'] ) && isset( $_COOKIE['name'] ) && check() == TRUE )
  {  print "Welcome.<br><a href='?quit'>Âûéòè</a>";  }

?>

В противном случае, пишем в куки те же значения и устанавливая время их жизни 15 минут, т.е. по сути мы продлеваем сессию еще на 15 минут. Ну и возвращаем true, как факт того, что пользователь имеет право смотреть авторизованную зону.

 7. Куки или сессии?

Тут уже выбирать вам что использовать. В принципе PHP сессию лучше использовать в большинстве случаем. Я использовал в данном примеру куки  дабы не захламлять голову начинающих программистов лишним кодом и головной болью.

Сам же скрипт, его смысл  алгоритм не претерпит существенных изменений. Алгоритм его работа останется тем же, просто проверять надо буде не Куки, а сессии...

8. Что дальше? Что можно усложнить?

Пунктов тут может быть множество. В качестве основных:
  1. Ограничение на попытки авторизации..
  2. Тоже, но при регистрации.
  3. Создание групп для пользователей и прав доступа уже внутри авторизированной зоны.
  4. Подтверждение регистрации, когда пользователю на указанный email отправляется письмо с указанием кода и ключа для подтверждения регистрации. Только после подтверждения регистрации пользователь может авторизоваться.
Хочу отметить еще один важный момент. Через некоторое время, в случае большого количества сессий таблица auth_sessions сильно увеличиться в объеме. Поэтому старые сессии необходимо через определенный промежуток в днях - удалять. Делается это PHP скриптом, который прописывается в CRON (планировщик задач в Юникс системах).

---------------------------------------------------

Ниже я прикрепляю полностью рабочий файл qwe.php с комментариями.
http://forum.vingrad.ru/index.php?act=modu...&attachid=1
PM ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "PHP"
Aliance
IZ@TOP
skyboy
SamDark
MoLeX

Новичкам:

  • PHP редакторы собираются и обсуждаются здесь
  • Электронные книги по PHP, документацию можно найти здесь
  • Интерпретатор PHP, полную документацию можно скачать на PHP.NET

Важно:

  • Не брезгуйте пользоваться тегами [code=php]КОД[/code] для повышения читабельности текста/кода.
  • Перед созданием новой темы воспользуйтесь поиском и загляните в FAQ
  • Действия модераторов можно обсудить здесь

Внимание:

  • Темы "ищу скрипт", "подскажите скрипт" и т.п. будут переноситься в форум "Web-технологии"
  • Темы с именами: "Срочно", "помогите", "не знаю как делать" будут УДАЛЯТЬСЯ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, IZ@TOP, skyboy, SamDark, MoLeX, awers.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | PHP: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0776 ]   [ Использовано запросов: 23 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.