Сегодня — финальная часть проекта по решению типичной задачи для фулстек-разработчика: сделать страницу авторизации и регистрации на сайте. На всякий случай напомним, кто такой фулстек и что мы уже сделали к этому моменту.
Фулстек-разработчик — это программист, который умеет работать как с фронтендом (визуальной частью сайта, тем, что мы видим в браузере), так и с бэкендом (настраивать сервер и писать служебные скрипты для внутренней логики сайта на сервере).
Вот что мы уже сделали в этом проекте:
- Создали страницы авторизации и регистрации на сайте.
- Настроили виртуальное окружение в Докере, чтобы можно было работать дальше с сайтом без переноса данных на сервер.
Теперь осталась финальная часть — написать бэкенд-скрипты и убедиться, что всё работает как нужно.
Исходные данные
Мы продолжим с того же места, где остановились прошлый раз:
- Установили LAMP на компьютер.
- Собрали и запустили LAMP-сборку в Докере.
- Убедились, что контейнер работает, — зашли на http://localhost и увидели стартовую страницу сборки:
Переводим все страницы на PHP
Для бэкенда мы будем использовать PHP — серверный язык программирования скриптов, который легко подключить к страницам, чтобы управлять их содержимым и доступом к ним. Чтобы на сервере всё отрабатывалось правильно, поменяем все расширения страниц с .html на .php и положим их в папку docker-compose-lamp/www
:
❗️ Также нам нужно заменить в этих файлах ссылки — вместо ссылок на HTML-страницы нужно поставить ссылки на их PHP-версии.
Откроем в браузере файл register.php — для этого перейдём по адресу http://localhost/register.php:
Страница открывается, все элементы на месте — это значит, что наше виртуальное окружение с PHP работает и мы можем продолжать настраивать серверную часть.
Создаём базу данных
В нашей сборке используется база данных MySQL — она считается стандартом в таких проектах, и с ней довольно просто работать. Нам нужна новая база данных, где мы создадим таблицу для хранения данных о пользователе — имени, почты, пароля, а также будем хранить там его уникальный номер.
В нашей LAMP-сборке уже есть база данных с именем docker — мы будем использовать её. Чтобы получить доступ к базе, открываем файл sample.env
, который лежит в папке docker-compose-lamp
:
Открываем этот файл в любом редакторе кода и смотрим в самый конец файла — находим там пароль суперпользователя (tiger) и название базы (docker):
Находим, как называется контейнер базой данных в Докере, — пишем в командной строке docker container ls
Видно, что для доступа к базе данных нам нужно обратиться к контейнеру lamp-mysql8. Теперь начинаем работать с базой данных — запускаем её в командном режиме и создадим нужную таблицу. В командной строке пишем:
docker exec -it lamp-mysql8 mysql -uroot -p
Вводим пароль суперпользователя (по умолчанию — tiger) и попадаем в пошаговый режим работы с MySQL. Вставляем туда такой код и нажимаем энтер — там мы создадим таблицу users с нужными полями:
USE docker;
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(75) NOT NULL,
`password` varchar(255) NOT NULL,
`email` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
Как всё будет готово — вводим команду exit; для завершения работы с базой данных.
Создаём файл настроек для работы с базой данных
Чтобы PHP на сервере мог работать с базой данных, нужно объяснить ему, как подключаться к базе: какие там логин, пароль и название базы. Но сложность в том, что нам нужно будет указать имя сервера не локального, а как он называется внутри Докера. Для этого снова заходим в базу данных
docker exec -it lamp-mysql8 mysql -uroot -p
И пишем команду SHOW VARIABLES WHERE Variable_name = 'hostname';
Копируем значение отсюда — оно нам пригодится на следующем шаге.
Теперь создаём в том же каталоге www файл config.php и заполняем его всеми данными:
<?php
define('DBSERVER', '5fe7130adcda'); // сервер с базой данных
define('DBUSERNAME', 'root'); // имя пользователя
define('DBPASSWORD', 'tiger'); // пароль
define('DBNAME', 'docker'); // название базы
/* соединяемся с базой */
$db = mysqli_connect(DBSERVER, DBUSERNAME, DBPASSWORD, DBNAME);
// проверяем соединение
if($db === false){
die("Ошибка соединения с базой. " . mysqli_connect_error());
}
?>
Создаём файлы сессии и её завершения
Сессия — это когда пользователь входит на сайт под своими логином и паролем. Чтобы при переходе по внутренним страницам сайта сервер не просил пользователя каждый раз их вводить, сервер запоминает состояние сессии. Мы тоже научим сервер делать такое — создадим для этого файл session.php
и добавим туда нужный код. Логика будет простая: начинаем сессию и, если логин и пароль правильные, — показываем внутреннюю страницу сайта.
<?php
// начинаем новую сессию
session_start();
// если логин и пароль верны — показываем стартовую внутреннюю страницу
if (isset($_SESSION["userid"]) && $_SESSION["userid"] === true) {
header("location: welcome.php");
exit;
}
?>
Когда пользователь нажмёт кнопку «выход», нам нужно закончить сессию. Для этого создадим файл logout.php
:
<?php
// стартуем сессию
session_start();
// закрываем сессию
if (session_destroy()) {
// перекидываем пользователя на страницу ввода логина и пароля
header("Location: login.php");
exit;
}
?>
Настраиваем скрипт формы регистрации
Задача скрипта регистрации — проверить все необходимые условия: правильно ли написан адрес почты, совпадают ли пароли, есть ли уже пользователь с такой почтой или нет. Если условия выполняются — добавляем такого пользователя в базу и выводим сообщение об успешной регистрации. Если нет — пишем, где что не так, чтобы пользователь мог это исправить.
Начнём с простого — добавим в файл register.php перед формой регистрации такую строчку: она отвечает за вывод ошибок или за сообщение об успешной регистрации:
<?php echo $error; ?>
Теперь добавим в самое начало этого файла PHP-скрипт. Мы добавили комментарии, чтобы было понятно, что там происходит:
<?php
// подключаем служебные файлы, которые создали раньше
require_once "config.php";
require_once "session.php";
// сообщение об ошибке, на старте — пустое
$error ='';
// если на странице нажали кнопку регистрации
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {
$error ='Ошибка';
// берём данные из формы
$fullname = trim($_POST['name']);
$email = trim($_POST['email']);
$password = trim($_POST['password']);
$confirm_password = trim($_POST["confirm_password"]);
$password_hash = password_hash($password, PASSWORD_BCRYPT);
if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
$error = '';
// указываем, что почта — это строка
$query->bind_param('s', $email);
$query->execute();
// сначала проверяем, есть ли такой аккаунт в базе
$query->store_result();
if ($query->num_rows > 0) {
$error .= '<p class="error">Пользователь с такой почтой уже зарегистрирован!</p>';
} else {
// проверяем требование к паролю
if (strlen($password ) < 6) {
$error .= '<p class="error">Пароль не должен быть короче 6 символов.</p>';
}
// проверяем, ввели ли пароль второй раз
if (empty($confirm_password)) {
$error .= '<p class="error">Пожалуйста, подтвердите пароль.</p>';
} else {
// если пароли не совпадают
if (empty($error) && ($password != $confirm_password)) {
$error .= '<p class="error">Введённые пароли не совпадают.</p>';
}
}
// если ошибок нет
if (empty($error) ) {
// добавляем запись в базу данных
$insertQuery = $db->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?);");
$insertQuery->bind_param("sss", $fullname, $email, $password_hash);
$result = $insertQuery->execute();
// если всё прошло успешно
if ($result) {
$error .= '<p class="success">Вы успешно зарегистрировались!</p>';
// если случилась ошибка
} else {
$error .= '<p class="error">Ошибка регистрации, что-то пошло не так.</p>';
}
}
}
}
// закрываем соединение с базой данных
mysqli_close($db);
}
?>
Для проверки зарегистрируем тестового пользователя с логином test@thecode.media. После нажатия кнопки браузер очистит поля в форме и выдаст сообщение о том, что мы успешно зарегистрировались:
Посмотрим, как обновилась таблица в базе данных MySQL, — перейдём в ней в режим команд и выполним SELECT * FROM users;
Видно, что в MySQL появилась новая запись и сгенерированный хеш пароля — значит, наша форма регистрации и скрипт работают.
Настраиваем страницу входа
Теперь, когда у нас есть логин и пароль, настроим скрипт на странице входа — там, где мы их вводим. Сделаем так, чтобы после успешного ввода мы попадали на внутреннюю страницу сайта. Логика будет такой же: добавляем перед формой строчку <?php echo $error; ?>
, а остальной скрипт добавляем в самое начало файла login.php
:
<?php
require_once "config.php";
require_once "session.php";
$error = '';
// если нажата кнопка входа
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {
$email = trim($_POST['email']);
$password = trim($_POST['password']);
// если не указана почта
if (empty($email)) {
$error .= '<p class="error">Введите адрес электронной почты.</p>';
}
// если не указан пароль
if (empty($password)) {
$error .= '<p class="error">Введите пароль.</p>';
}
// если ошибок нет
if (empty($error)) {
// берём данные пользователя
if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
$query->bind_param('s', $email);
$query->execute();
$row = $query->get_result()->fetch_assoc();
// смотрим, есть ли такой пользователь в базе
if ($row) {
// если пароль правильный
if (password_verify($password, $row['password'])) {
// стартуем новую сессию
$_SESSION["userid"] = $row['id'];
$_SESSION["user"] = $row;
// перенаправляем пользователя на внутреннюю страницу
header("location: welcome.php");
exit;
// если пароль не подходит
} else {
$error .= '<p class="error">Введён неверный пароль.</p>';
}
// если пользователя нет в базе
} else {
$error .= '<p class="error">Нет пользователя с таким адресом электронной почты.</p>';
}
}
}
// закрываем соединение с базой данных
mysqli_close($db);
}
?>
Обновляем страницу и сначала пробуем зайти с неправильными данными:
А теперь — с правильными:
Добавляем сессию во внутренний файл welcome.php
Чтобы неавторизованный пользователь не мог получить доступ к внутренней странице, добавим проверку на активную сессию: если её нет, то перенаправляем пользователя на страницу входа. Для этого в начало файла welcome.php добавим такой скрипт:
<?php
// стартуем сессию
session_start();
// если пользователь не авторизован, то перенаправляем его на страницу входа
if (!isset($_SESSION["userid"]) || $_SESSION["userid"] !== true) {
header("location: login.php");
exit;
}
?>
Теперь неавторизованный пользователь не сможет получить доступ к нашей внутренней странице — его сразу перекинет на страницу авторизации. Это значит, что мы справились с нашей фулстек-задачей: сделать и настроить страницу авторизации и регистрации на сайте. Поздравляем с успешным финалом проекта!
<?php
// подключаем служебные файлы, которые создали раньше
require_once "config.php";
require_once "session.php";
// сообщение об ошибке, на старте — пустое
$error ='';
// если на странице нажали кнопку регистрации
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {
$error ='Ошибка';
// берём данные из формы
$fullname = trim($_POST['name']);
$email = trim($_POST['email']);
$password = trim($_POST['password']);
$confirm_password = trim($_POST["confirm_password"]);
$password_hash = password_hash($password, PASSWORD_BCRYPT);
if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
$error = '';
// указываем, что почта — это строка
$query->bind_param('s', $email);
$query->execute();
// сначала проверяем, есть ли такой аккаунт в базе
$query->store_result();
if ($query->num_rows > 0) {
$error .= '<p class="error">Пользователь с такой почтой уже зарегистрирован!</p>';
} else {
// проверяем требование к паролю
if (strlen($password ) < 6) {
$error .= '<p class="error">Пароль не должен быть короче 6 символов.</p>';
}
// проверяем, ввели ли пароль второй раз
if (empty($confirm_password)) {
$error .= '<p class="error">Пожалуйста, подтвердите пароль.</p>';
} else {
// если пароли не совпадают
if (empty($error) && ($password != $confirm_password)) {
$error .= '<p class="error">Введённые пароли не совпадают.</p>';
}
}
// если ошибок нет
if (empty($error) ) {
// добавляем запись в базу данных
$insertQuery = $db->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?);");
$insertQuery->bind_param("sss", $fullname, $email, $password_hash);
$result = $insertQuery->execute();
// если всё прошло успешно
if ($result) {
$error .= '<p class="success">Вы успешно зарегистрировались!</p>';
// если случилась ошибка
} else {
$error .= '<p class="error">Ошибка регистрации, что-то пошло не так.</p>';
}
}
}
}
// закрываем соединение с базой данных
mysqli_close($db);
}
?>
<!DOCTYPE html>
<html lang="form-group">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
<!-- подключаем бутстрап -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>
<body>
<!-- вся страница будет в одном контейнере -->
<div class="container">
<div class="row">
<!-- делаем самую простую вёрстку -->
<div class="col-md-12">
<h2>Регистрация</h2>
<p>Заполните все поля, чтобы создать новый аккаунт.</p>
<?php echo $error; ?>
<!-- форма регистрации -->
<form action="" method="post">
<!-- поле ввода имени -->
<div class="form-group">
<label>Имя</label>
<input type="text" name="name" class="form-control" required>
</div>
<!-- поле ввода электронной почты -->
<div class="form-group">
<label>Электронная почта</label>
<input type="email" name="email" class="form-control" required />
</div>
<!-- поле ввода пароля -->
<div class="form-group">
<label>Пароль</label>
<input type="password" name="password" class="form-control" required>
</div>
<!-- поле повторного ввода пароля -->
<div class="form-group">
<label>Повторите пароль</label>
<input type="password" name="confirm_password" class="form-control" required>
</div>
<!-- кнопка отправки данных на сервер -->
<div class="form-group">
<input type="submit" name="submit" class="btn btn-primary" value="Зарегистрироваться">
</div>
<!-- вход для тех, у кого уже есть аккаунт -->
<p>Уже зарегистрированы? <a href="login.php">Войдите в систему</a>.</p>
</form>
</div>
</div>
</div>
</body>
</html>
<?php
require_once "config.php";
require_once "session.php";
$error = '';
$qq = '';
// если нажата кнопка входа
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['submit'])) {
$email = trim($_POST['email']);
$password = trim($_POST['password']);
// если не указана почта
if (empty($email)) {
$error .= '<p class="error">Введите адрес электронной почты.</p>';
}
// если не указан пароль
if (empty($password)) {
$error .= '<p class="error">Введите пароль.</p>';
}
// если ошибок нет
if (empty($error)) {
// берём данные пользователя
if($query = $db->prepare("SELECT * FROM users WHERE email = ?")) {
$query->bind_param('s', $email);
$query->execute();
$row = $query->get_result()->fetch_assoc();
// смотрим, есть ли такой пользователь в базе
if ($row) {
// если пароль правильный
if (password_verify($password, $row['password'])) {
// начинаем новую сессию
$_SESSION["userid"] = $row['id'];
$_SESSION["user"] = $row;
// перенаправляем пользователя на внутреннюю страницу
header("location: welcome.php");
exit;
// если пароль не подходит
} else {
$error .= '<p class="error">Введён неверный пароль.</p>';
}
// если пользователя нет в базе
} else {
$error .= '<p class="error">Нет пользователя с таким адресом электронной почты.</p>';
}
}
}
// закрываем соединение с базой данных
mysqli_close($db);
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Вход</title>
<!-- подключаем бутстрап -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>
<body>
<!-- создаём контейнер -->
<div class="container">
<div class="row">
<!-- указываем стиль адаптивной вёрстки -->
<div class="col-md-12">
<!-- пишем заголовок и пояснительный текст -->
<h2>Вход</h2>
<p>Введите свою почту и пароль.</p>
<?php echo $error; ?>
<!-- метод, которым будем работать с формой, — отправлять на сервер -->
<form action="" method="post">
<!-- поле ввода электронной почты -->
<div class="form-group">
<label>Электронная почта</label>
<input type="email" name="email" class="form-control" required />
</div>
<!-- поле ввода пароля -->
<div class="form-group">
<label>Пароль</label>
<input type="password" name="password" class="form-control" required>
</div>
<!-- кнопка отправки данных на сервер -->
<div class="form-group">
<input type="submit" name="submit" class="btn btn-primary" value="Войти">
</div>
<!-- ссылка для тех, у кого ещё нет аккаунта -->
<p>Нет аккаунта? <a href="register.php">Создайте его за минуту</a>.</p>
</form>
</div>
</div>
</div>
</body>
</html>
<?php
// начинаем сессию
session_start();
// если пользователь не авторизован, то перенаправляем его на страницу входа
if (!isset($_SESSION["userid"]) || $_SESSION["userid"] !== true) {
header("location: login.php");
exit;
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Добро пожаловать</title>
<!-- подключаем бутстрап -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>
<body>
<!-- создаём контейнер -->
<div class="container">
<div class="row">
<!-- указываем стиль адаптивной вёрстки -->
<div class="col-md-12">
<!-- пишем заголовок и пояснительный текст -->
<h2>Привет! Добро пожаловать на сайт.</h2>
<p>Внутри сайта может быть любое наполнение — можно оформлять как угодно и добавлять любой контент</p>
<p>
<!-- кнопка для выхода -->
<a href="logout.php" class="btn btn-primary btn-lg active" role="button" aria-pressed="true">Выход</a>
</p>
</div>
</div>
</div>
</body>
</html>