Авторизация на сессиях PHP и MySQL

Август 3rd, 2011 Рубрики: MySQL, PHP coubertin.cz www.colvillewoodworking.com


Итак, как я и обещал, сегодня я расскажу как сделать свою авторизацию используя 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>&nbsp;</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>&nbsp;</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>");
?>

Тоже все просто, подключаем конфиг, проверяем авторизован ли и выводи данные пользователя.

Ну вот собственно и все, хотя все довольно просто — пожалуй это самая длинная моя статья в блоге на данный момент. Если что то не понятно — спрашивайте! И если вы нашли ошибки или есть замечания, не молчите 🙂

Теги: , , ,

90 комментариев к “Авторизация на сессиях PHP и MySQL”

  1. Mihail
    Март 23rd, 2013 at 22:15
    1

    <>>

    1 Как сделать?

    <>

    2 Звучит грозднно. Как исправить?

    [Ответить]

  2. Mihail
    Март 23rd, 2013 at 22:16
    2

    Спасибо, замечательная статья! Но есть один вопрос: разве хорошо хранить пароль в базе в открытом виде? Не правильнее будет хранить хэш?

    1 Как сделать?

    один большой недочёт — возможность MySQL-инъекции

    2 Звучит грозднно. Как исправить?

    [Ответить]

    ZekMan Reply:

    Данная статья является всего лишь примером логики.
    Для того что бы использовать хеш (с солью раз уж на то пошло), везде где идет сверка пароля с бд или добавление в бд сделать что то типа $password=md5($pass.’soil’);
    Избежать SQL инъекций можно при помощи использования PDO или просто экранируя данные например используя mysql_real_escape_string

    [Ответить]

  3. ZekMan
    Март 24th, 2013 at 15:54
    3

    Тут логика в том что бы передавать аргумент (yes|no) дабы указать, могут ли гости (не авторизованные пользователи) видеть эту страницу или нет.

    [Ответить]

  4. Сергей
    Март 29th, 2013 at 14:45
    4

    Очень рекомендую автора данной статьи почитать по ссылке http://www.php.net/manual/ru/book.mysqli.php . Очень удобно работать с базой данных)

    Да ещё. Если посмотреть файл настроек php.ini то можно вместо

    использовать

    [Ответить]

    ZekMan Reply:

    Лучше использовать PDO

    [Ответить]

  5. Vladislav
    Апрель 30th, 2013 at 16:13
    5

    Здравствуйте. Почему мне в файле functions.php 3 строка выдает ошибку
    Parse error: syntax error, unexpected T_STRING, expecting ‘(‘ in C:\apache\localhost\www\curse3\functions.php on line 3
    Простите за глупый вопрос.

    [Ответить]

    ZekMan Reply:

    Ну давайте для начала вы загрузите этот кусок кода хотя бы на http://govnokod.com/

    [Ответить]

  6. Pavel
    Май 23rd, 2013 at 19:55
    6

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

    [Ответить]

    ZekMan Reply:

    в $_SESSION есть логин, можно делать запрос по нему. А лучше добавить в сессию еще и id пользователя

    [Ответить]

  7. Май 30th, 2013 at 18:52
    7

    Разве целесообразно в наше время использовать глобальные переменные?
    В данном стиле программирования при использовании файла config.php есть смысл задать константы через define() — Что-то типа такого:

    1
    2
    3
    4
    define("DB_HOST", "your_database_host_address");
    define("DB_USER", "database_login_name");
    define("DB_PASS", "database_password");
    define("DB_DATA", "database_title");

    [Ответить]

    ZekMan Reply:

    Не очень, но что либо переписывать в данном скрипте я не хочу. Он сделан как пример логики, но не более.

    [Ответить]

  8. Евгений
    Июнь 22nd, 2013 at 12:12
    8

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

    [Ответить]

    ZekMan Reply:

    да, можно код?

    [Ответить]

  9. Евгений
    Июнь 22nd, 2013 at 13:34
    9

    не отправляется код

    [Ответить]

    ZekMan Reply:

    Воспользуйтесь сервисом для обмена кодом. Например http://govnokod.com/

    [Ответить]

  10. Евгений
    Июнь 22nd, 2013 at 14:03
    10
  11. Никита
    Август 6th, 2013 at 21:28
    11

    Напишите смену пароля

    [Ответить]

    ZekMan Reply:

    Обязательно это сделаю))

    [Ответить]

  12. Никита
    Август 7th, 2013 at 09:13
    12

    А когда примерно будет она готова?

    [Ответить]

  13. MIK
    Сентябрь 21st, 2013 at 22:52
    13

    Выше сказано

    1
    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:

    Тут моя вина, ошибся.
    Что по поводу того что бы «видеть» пароль — я против такого подхода. Вообще хранить в открытом виде пароль я бы не рекомендовал.
    Получить можно 🙂 Используя радужные таблицы например. Поэтому и нужна шифровка пароля, хэшем с солью. Хэш с солью это вообще тот минимум который нужен.

    [Ответить]

    ZekMan Reply:

    И очень вас прошу — не пишите транслитом, коммент упал в спам вообще

    [Ответить]

  14. Римма
    Октябрь 27th, 2013 at 23:23
    14

    Спасибо! Полезные уроки! Жду продолжения (смена пароля).

    [Ответить]

    ZekMan Reply:

    http://programmer-weekdays.ru/archives/395

    [Ответить]

  15. Батя
    Ноябрь 15th, 2013 at 04:08
    15

    После того как перелопатил кучу статей, наткнулся на эту. Все красиво, доступно, даже такой чайник как я вроде бы все понял. Но на практике 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:

    По поводу ошибок.
    По поводу headers already sent уже не раз писал что старт сессии должен происходить в самом начале, до вывода чего либо, а файл должен быть сохранен без BOM. Подробно что и почему почитайте тут http://phpfaq.ru/headers
    По второй и третьей ошибке — он прямым текстом говорит что не может найти файл или директорию — проверяйте путь и название файлов.
    Пятая ошибка появляется соответственно из за предыдущих двух.

    [Ответить]

  16. Arime
    Ноябрь 17th, 2013 at 14:24
    16

    После регистрации я автонризируюсь, ввожу логин и пароль. Пишет невырный логин или пароль. Хотя запись в базе данных есть. В чем дело может быть?

    [Ответить]

    ZekMan Reply:

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

    [Ответить]

  17. Батя
    Ноябрь 17th, 2013 at 23:58
    17

    Сорри, все правильно. Проблема решена! Виной всему усталость)) Вывод: отдыхать тоже нужно))

    [Ответить]

  18. Батя
    Ноябрь 18th, 2013 at 00:00
    18

    Статья отличная! Применил почти все, что тут есть! Автору огромное сенкью!

    [Ответить]

  19. Батя
    Ноябрь 21st, 2013 at 11:35
    19

    При авторизации на login.php все проходит отлично, попадаю на страничку main.php, После logout на нее уже не пускает, т.е. все отлично. Проблема в том, что есть еще page1.php и page2.php. Как и где тока не вставлял checkLoggedIn, везде натыкаюсь на ошибки. Подскажите что не так делаю. Спасибо

    [Ответить]

    ZekMan Reply:

    Для начала хотелось бы увидеть какие именно ошибки возникают

    [Ответить]

  20. Алексей
    Январь 30th, 2014 at 10:12
    20

    А как добавить проверку русских букв? Добавляю в массив еще одну валидацию:

    1
    "alpha_rus"=>"^[а-яА-Я]+$",

    В join.php

    1
    field_validator("Имя",  $_POST["name"],   "alpha_rus", 1, 30);

    Ошибка:
    Пожалуйста введите нормальный Имя.

    [Ответить]

  21. Алексей
    Январь 30th, 2014 at 10:40
    21

    Нашел решение..
    «alpha_rus»=>»^[АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя]+$»,

    [Ответить]

  22. Чаки
    Февраль 4th, 2014 at 19:08
    22

    а скиньте исходники в одном файле

    [Ответить]

    ZekMan Reply:

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

    [Ответить]

  23. влад
    Февраль 11th, 2014 at 22:19
    23

    А как сделать скачивания файла только для зарегевших пользователей
    заранее спс

    [Ответить]

    ZekMan Reply:

    Довольно просто. Вам нужна страничка под авторизацией куда мы параметром передаем уникальную строку/номер который идентифицирует файл. Далее мы проверяем авторизацию, наличие файла и при помощи header запускаем скачивание файла. Примеров в интернете довольно много

    [Ответить]

  24. djniktih
    Февраль 16th, 2014 at 17:27
    24

    Я подучил PHP, вижу тут море sql-inj! правь)

    [Ответить]

Страницы комментариев

Написать комментарий