Авторизация на сессиях PHP и MySQL
Итак, как я и обещал, сегодня я расскажу как сделать свою авторизацию используя session, php и mysql. Для начала определимся что такое сессия и чем она отличается от cookie.
Session – это механизм, позволяющий однозначно идентифицировать браузер и создающий для этого браузера файл на сервере, в котором хранятся переменные сеанса.
Cookies — это механизм хранения данных броузером удаленного компьютера для идентификации возвращающихся посетителей и хранения параметров веб-страниц.
Т.е. главное различие это место хранения данных, у сессий на стороне сервера, у куков на стороне клиента, это различие критично. Если украсть у пользователя cookie довольно просто то с сессиями не все так просто. Ну а теперь перейдем к практической части а именно к написанию своей авторизации.
Для начала определим имена файлов:
- config.php — хранит данные для подключения к Базе Данных ( далее БД )
- functions.php — содержит в себе все функции для работы авторизации
- join.php — простейший пример регистрации пользователя в системе
- login.php — служит для входа в систему
- logout.php — служит для выхода из системы
- members.php — служит для проверки авторизации ( простейший пример «закрытой» части сайта
Для начала создадим БД и таблицу где будут храниться данные пользователей.
SQL дамп таблицы пользователей
1 2 3 4 5 6 | CREATE TABLE users ( id INT(5) NOT NULL AUTO_INCREMENT, login VARCHAR(15) DEFAULT '0' , password VARCHAR(15) DEFAULT '0' , PRIMARY KEY (id) ); |
config.php
Что содержит данный файл я уже говорил, поэтому просто приведу его код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php # Запуск сессии session_start(); # Служит для отладки, показывает все ошибки, предупреждения и т.д. error_reporting(E_ALL); # Подключение файлов с функциями include_once("functions.php"); # В этом массиве далее мы будем хранить сообщения системы, т.е. ошибки. $messages=array(); # Данные для подключения к БД $dbhost="localhost"; $dbuser="database_user"; $dbpass="user_password"; $dbname="datebase"; # Вызываем функцию подключения к БД connectToDB(); ?> |
functions.php
Самый большой файл из всех в данной статье, содержит все функции. Приведу исходный код а потом прокомментирую каждую функцию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | <?php function connectToDB() { global $link, $dbhost, $dbuser, $dbpass, $dbname; ($link = mysql_pconnect("$dbhost", "$dbuser", "$dbpass")) || die("Couldn't connect to MySQL"); mysql_select_db("$dbname", $link) || die("Couldn't open db: $dbname. Error if any was: ".mysql_error() ); } function newUser($login, $password) { global $link; $query="INSERT INTO users (login, password) VALUES('$login', '$password')"; $result=mysql_query($query, $link) or die("Died inserting login info into db. Error returned if any: ".mysql_error()); return true; } function displayErrors($messages) { print("<b>Возникли следующие ошибки:</b>\n<ul>\n"); foreach($messages as $msg){ print("<li>$msg</li>\n"); } print("</ul>\n"); } function checkLoggedIn($status){ switch($status){ case "yes": if(!isset($_SESSION["loggedIn"])){ header("Location: login.php"); exit; } break; case "no": if(isset($_SESSION["loggedIn"]) && $_SESSION["loggedIn"] === true ){ header("Location: members.php"); } break; } return true; } function checkPass($login, $password) { global $link; $query="SELECT login, password FROM users WHERE login='$login' and password='$password'"; $result=mysql_query($query, $link) or die("checkPass fatal error: ".mysql_error()); if(mysql_num_rows($result)==1) { $row=mysql_fetch_array($result); return $row; } return false; } function cleanMemberSession($login, $password) { $_SESSION["login"]=$login; $_SESSION["password"]=$password; $_SESSION["loggedIn"]=true; } function flushMemberSession() { unset($_SESSION["login"]); unset($_SESSION["password"]); unset($_SESSION["loggedIn"]); session_destroy(); return true; } function field_validator($field_descr, $field_data, $field_type, $min_length="", $max_length="", $field_required=1) { global $messages; if(!$field_data && !$field_required){ return; } $field_ok=false; $email_regexp="^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|"; $email_regexp.="(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"; $data_types=array( "email"=>$email_regexp, "digit"=>"^[0-9]$", "number"=>"^[0-9]+$", "alpha"=>"^[a-zA-Z]+$", "alpha_space"=>"^[a-zA-Z ]+$", "alphanumeric"=>"^[a-zA-Z0-9]+$", "alphanumeric_space"=>"^[a-zA-Z0-9 ]+$", "string"=>"" ); if ($field_required && empty($field_data)) { $messages[] = "Поле $field_descr является обезательным"; return; } if ($field_type == "string") { $field_ok = true; } else { $field_ok = ereg($data_types[$field_type], $field_data); } if (!$field_ok) { $messages[] = "Пожалуйста введите нормальный $field_descr."; return; } if ($field_ok && ($min_length > 0)) { if (strlen($field_data) < $min_length) { $messages[] = "$field_descr должен быть не короче $min_length символов."; return; } } if ($field_ok && ($max_length > 0)) { if (strlen($field_data) > $max_length) { $messages[] = "$field_descr не должен быть длиннее $max_length символов."; return; } } } ?> |
А теперь по порядку
- function connectToDB() — служит для подключения к базе данных
- function newUser($login, $password) — служит для создания нового пользователя в системе
- function displayErrors($messages) — выводит массив ошибок
- function checkLoggedIn($status) — проверяет авторизацию пользователя.
- function checkPass($login, $password) — проверяет пользователя по БД во время авторизации
- function cleanMemberSession($login, $password) — авторизует пользователя
- function flushMemberSession() — выход, или если вам будет удобнее logout
- function field_validator($field_descr, $field_data, $field_type, $min_length=»», $max_length=»», $field_required=1) — Валидатор данных, проверяет соответствие полей требованиям системы
Работу каждой функции я описывать не буду, т.к. они довольно простые, в данный момент нас интересует только логика. Если будут вопросы — спрашивайте.
join.php
Итак исходник:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | <?php include_once("config.php"); checkLoggedIn("no"); $title="страница регистрации"; if(isset($_POST["submit"])){ field_validator("login name", $_POST["login"], "alphanumeric", 4, 15); field_validator("password", $_POST["password"], "string", 4, 15); field_validator("confirmation password", $_POST["password2"], "string", 4, 15); if(strcmp($_POST["password"], $_POST["password2"])) { $messages[]="Ваши пароли не совпадают"; } $query="SELECT login FROM users WHERE login='".$_POST["login"]."'"; $result=mysql_query($query, $link) or die("MySQL query $query failed. Error if any: ".mysql_error()); if( ($row=mysql_fetch_array($result)) ){ $messages[]="Логин \"".$_POST["login"]."\" уже занят, попробуйте другой."; } if(empty($messages)) { newUser($_POST["login"], $_POST["password"]); cleanMemberSession($_POST["login"], $_POST["password"]); header("Location: members.php"); } } ?> <html> <head> <title><?php print $title; ?></title> <meta http-equiv="Content-Type" content="text/html; charset=uft-8"> </head> <body> <h1><?php print $title; ?></h1> <?php if(!empty($messages)){ displayErrors($messages); } ?> <form action="<?php print $_SERVER["PHP_SELF"]; ?>" method="POST"> <table> <tr><td>Логин:</td><td><input type="text" name="login" value="<?php print isset($_POST["login"]) ? $_POST["login"] : "" ; ?>" maxlength="15"></td></tr> <tr><td>Пароль:</td><td><input type="password" name="password" value="" maxlength="15"></td></tr> <tr><td>Повторить пароль:</td><td><input type="password" name="password2" value="" maxlength="15"></td></tr> <tr><td> </td><td><input name="submit" type="submit" value="Submit"></td></tr> </table> </form> </body> </html> |
Если кратко описать работу скрипта получится что то вроде:
1. Если уже авторизованы пересылаем на members.php ( строка 4 )
2. Если существует $_POST[‘submit’] ( если отправили данные с формы ) проверяем поля валидатором, проверяем наличие такого пользователя, если никаких ошибок нет, добавляем нового пользователя, ставим сессию и пускаем на members.php
3. Если есть ошибки — выводим
4. Выводим форму
login.php
Код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | <?php include_once("config.php"); checkLoggedIn("no"); $title="Страница авторизации"; if(isset($_POST["submit"])) { field_validator("login name", $_POST["login"], "alphanumeric", 4, 15); field_validator("password", $_POST["password"], "string", 4, 15); if($messages){ doIndex(); exit; } if( !($row = checkPass($_POST["login"], $_POST["password"])) ) { $messages[]="Incorrect login/password, try again"; } if($messages){ doIndex(); exit; } cleanMemberSession($row["login"], $row["password"]); header("Location: members.php"); } else { doIndex(); } function doIndex() { global $messages; global $title; ?> <html> <head> <title><?php print $title; ?></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> <h1><?php print $title; ?></h1> <?php if($messages) { displayErrors($messages); } ?> <form action="<?php print $_SERVER["PHP_SELF"]; ?>" method="POST"> <table> <tr><td>Логин:</td><td><input type="text" name="login" value="<?php print isset($_POST["login"]) ? $_POST["login"] : "" ; ?>" maxlength="15"></td></tr> <tr><td>Пароль:</td><td><input type="password" name="password" value="" maxlength="15"></td></tr> <tr><td> </td><td><input name="submit" type="submit" value="Submit"></td></tr> </table> </form> </body> </html> <?php } ?> |
Во первых тут стоит указать что вывод html и ошибок происходит в функции doIndex() которая вызывается в некоторых случаях, это не очень удобно поэтому кто хочет переписать — милости прошу, лично я сделал это для примера, к каждому проекту я пишу собственную авторизацию и стараюсь не повторяться. Поэтому здесь только пример.
А теперь по порядку.
1. подключаем конфиг
2. если уже авторизованы пересылаем на страницу members.php ( функция checkLoggedIn с параметром no )
3. Если отправлена форма, проверяем поля валидатором, если есть ошибки вызываем функцию doIndex(), если пароли не совпадают ставим ошибку, если есть ошибки вызываем функцию doIndex(), если все в порядке ставим сессию и отправляем на members.php, иначе опять вызываем функцию doIndex()
4. Функция doIndex() выводит html код, ошибки и форму для авторизации.
logout.php
1 2 3 4 5 6 | <?php include_once("config.php"); checkLoggedIn("yes"); flushMemberSession(); header("Location: login.php"); ?> |
Тут все просто:
1. Подключаем конфиг
2. Проверяем авторизован ли пользователь
3. Уничтожаем сессию
4. Отправляем пользователя на страницу авторизации
members.php
1 2 3 4 5 6 7 | <?php include_once("config.php"); checkLoggedIn("yes"); print("<b>".$_SESSION["login"]."</b>! Добро пожаловать<br>\n"); print("Ваш пароль: <b>".$_SESSION["password"]."</b><br>\n"); print("<a href=\"logout.php"."\">Выход</a>"); ?> |
Тоже все просто, подключаем конфиг, проверяем авторизован ли и выводи данные пользователя.
Ну вот собственно и все, хотя все довольно просто — пожалуй это самая длинная моя статья в блоге на данный момент. Если что то не понятно — спрашивайте! И если вы нашли ошибки или есть замечания, не молчите 🙂
<>>
1 Как сделать?
<>
2 Звучит грозднно. Как исправить?
[Ответить]
Спасибо, замечательная статья! Но есть один вопрос: разве хорошо хранить пароль в базе в открытом виде? Не правильнее будет хранить хэш?
1 Как сделать?
один большой недочёт — возможность MySQL-инъекции
2 Звучит грозднно. Как исправить?
[Ответить]
ZekMan Reply:
Март 24th, 2013 at 15:52
Данная статья является всего лишь примером логики.
Для того что бы использовать хеш (с солью раз уж на то пошло), везде где идет сверка пароля с бд или добавление в бд сделать что то типа $password=md5($pass.’soil’);
Избежать SQL инъекций можно при помощи использования PDO или просто экранируя данные например используя mysql_real_escape_string
[Ответить]
Тут логика в том что бы передавать аргумент (yes|no) дабы указать, могут ли гости (не авторизованные пользователи) видеть эту страницу или нет.
[Ответить]
Очень рекомендую автора данной статьи почитать по ссылке http://www.php.net/manual/ru/book.mysqli.php . Очень удобно работать с базой данных)
Да ещё. Если посмотреть файл настроек php.ini то можно вместо
использовать
[Ответить]
ZekMan Reply:
Апрель 1st, 2013 at 16:24
Лучше использовать PDO
[Ответить]
Здравствуйте. Почему мне в файле functions.php 3 строка выдает ошибку
Parse error: syntax error, unexpected T_STRING, expecting ‘(‘ in C:\apache\localhost\www\curse3\functions.php on line 3
Простите за глупый вопрос.
[Ответить]
ZekMan Reply:
Май 13th, 2013 at 07:10
Ну давайте для начала вы загрузите этот кусок кода хотя бы на http://govnokod.com/
[Ответить]
Когда пользователь авторизовался, как получить его id? Чтобы по нему открыть личный кабинет. id хранится в той же таблице, что и юзер.
[Ответить]
ZekMan Reply:
Май 25th, 2013 at 06:27
в $_SESSION есть логин, можно делать запрос по нему. А лучше добавить в сессию еще и id пользователя
[Ответить]
Разве целесообразно в наше время использовать глобальные переменные?
В данном стиле программирования при использовании файла config.php есть смысл задать константы через define() — Что-то типа такого:
2
3
4
define("DB_USER", "database_login_name");
define("DB_PASS", "database_password");
define("DB_DATA", "database_title");
[Ответить]
ZekMan Reply:
Июнь 3rd, 2013 at 21:20
Не очень, но что либо переписывать в данном скрипте я не хочу. Он сделан как пример логики, но не более.
[Ответить]
всем доброго дня, у меня такая проблема, на сайте не работает авторизация, ввожу верные данные, но на страницу пользователя не заходит, регистрация работает нормально, пользователь заносится в БД, пароли хешируются, если нужно могу скопировать php код авторизации, синтаксических ошибок нет, всё перепроверил несколько раз, сайт делаю на локальном сервере денвер
[Ответить]
ZekMan Reply:
Июнь 22nd, 2013 at 12:23
да, можно код?
[Ответить]
не отправляется код
[Ответить]
ZekMan Reply:
Июнь 22nd, 2013 at 13:54
Воспользуйтесь сервисом для обмена кодом. Например http://govnokod.com/
[Ответить]
http://www.govnokod.com/7243
[Ответить]
Напишите смену пароля
[Ответить]
ZekMan Reply:
Август 7th, 2013 at 05:19
Обязательно это сделаю))
[Ответить]
А когда примерно будет она готова?
[Ответить]
Выше сказано
2
3
4
5
6
functions.php
1. функция checkPass(), добавить в начало строку вида $password=md5($password.’soil’);
2. функция cleanMemberSession(), изменить строку с паролем на $_SESSION["password"]=md5($password.’soil’);
3. функция newUser(), добавить в начало строку вида $password=md5($password.’soil’);
Вроде все
Error!!!
В функции cleanMemberSession() менять ничего не надо! Иначе мы получаем не хэш пароля из базы, а хэш хэша, что в базе!
Тут наоборот нужна функция типа DECRIPT что бы мы видели свой пароль. Но мы знаем что получить из хэша пароль «почти» невозможно.
Поэтому в cleanMemberSession() ничего не меняем.
[Ответить]
ZekMan Reply:
Октябрь 12th, 2013 at 14:33
Тут моя вина, ошибся.
Что по поводу того что бы «видеть» пароль — я против такого подхода. Вообще хранить в открытом виде пароль я бы не рекомендовал.
Получить можно 🙂 Используя радужные таблицы например. Поэтому и нужна шифровка пароля, хэшем с солью. Хэш с солью это вообще тот минимум который нужен.
[Ответить]
ZekMan Reply:
Октябрь 12th, 2013 at 14:33
И очень вас прошу — не пишите транслитом, коммент упал в спам вообще
[Ответить]
Спасибо! Полезные уроки! Жду продолжения (смена пароля).
[Ответить]
ZekMan Reply:
Октябрь 28th, 2013 at 01:39
http://programmer-weekdays.ru/archives/395
[Ответить]
После того как перелопатил кучу статей, наткнулся на эту. Все красиво, доступно, даже такой чайник как я вроде бы все понял. Но на практике login.php уже при запуске выдает несколько ошибок:
Во первых, в углу появляется это: п»ї
и далее:
Warning: session_start() [function.session-start]: Cannot send session cookie — headers already sent by (output started at Z:\home\bbb\www\login.php:9) in Z:\home\bbb\www\config.php on line 3
Warning: session_start() [function.session-start]: Cannot send session cache limiter — headers already sent (output started at Z:\home\bbb\www\login.php:9) in Z:\home\bbb\www\config.php on line 3
Warning: include_once(functions.php) [function.include-once]: failed to open stream: No such file or directory in Z:\home\bbb\www\config.php on line 7
Warning: include_once() [function.include]: Failed opening ‘functions.php’ for inclusion (include_path=’.;/usr/local/php5/PEAR’) in Z:\home\bbb\www\config.php on line 7
Fatal error: Call to undefined function connecttodb() in Z:\home\bbb\www\config.php on line 16
Хотелось бы понять что не так делаю. Заранее спасибо
[Ответить]
ZekMan Reply:
Ноябрь 17th, 2013 at 05:14
По поводу ошибок.
По поводу headers already sent уже не раз писал что старт сессии должен происходить в самом начале, до вывода чего либо, а файл должен быть сохранен без BOM. Подробно что и почему почитайте тут http://phpfaq.ru/headers
По второй и третьей ошибке — он прямым текстом говорит что не может найти файл или директорию — проверяйте путь и название файлов.
Пятая ошибка появляется соответственно из за предыдущих двух.
[Ответить]
После регистрации я автонризируюсь, ввожу логин и пароль. Пишет невырный логин или пароль. Хотя запись в базе данных есть. В чем дело может быть?
[Ответить]
ZekMan Reply:
Ноябрь 18th, 2013 at 15:02
Сделайте выводы на экран, проверьте соответствуют ли они тем что находятся в базе. Эта статья устарела, посмотрите в моем блоге более новую версию.
[Ответить]
Сорри, все правильно. Проблема решена! Виной всему усталость)) Вывод: отдыхать тоже нужно))
[Ответить]
Статья отличная! Применил почти все, что тут есть! Автору огромное сенкью!
[Ответить]
При авторизации на login.php все проходит отлично, попадаю на страничку main.php, После logout на нее уже не пускает, т.е. все отлично. Проблема в том, что есть еще page1.php и page2.php. Как и где тока не вставлял checkLoggedIn, везде натыкаюсь на ошибки. Подскажите что не так делаю. Спасибо
[Ответить]
ZekMan Reply:
Ноябрь 23rd, 2013 at 16:32
Для начала хотелось бы увидеть какие именно ошибки возникают
[Ответить]
А как добавить проверку русских букв? Добавляю в массив еще одну валидацию:
В join.php
Ошибка:
Пожалуйста введите нормальный Имя.
[Ответить]
Нашел решение..
«alpha_rus»=>»^[АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя]+$»,
[Ответить]
а скиньте исходники в одном файле
[Ответить]
ZekMan Reply:
Февраль 12th, 2014 at 06:10
Для этой статьи у меня не сохранилось исходников. Посмотрите более свежую версию этой авторизации в моем блоге, я там оставлял исходники.
[Ответить]
А как сделать скачивания файла только для зарегевших пользователей
заранее спс
[Ответить]
ZekMan Reply:
Февраль 12th, 2014 at 06:09
Довольно просто. Вам нужна страничка под авторизацией куда мы параметром передаем уникальную строку/номер который идентифицирует файл. Далее мы проверяем авторизацию, наличие файла и при помощи header запускаем скачивание файла. Примеров в интернете довольно много
[Ответить]
Я подучил PHP, вижу тут море sql-inj! правь)
[Ответить]