Создание системы логина на PHP
Основные подходы к реализации входа
Как организовать безопасный вход с использованием PDO и сессий?
Этот способ является базовым и рекомендуемым для большинства проектов. Он использует расширение PDO для взаимодействия с базой данных, функцию password_verify для проверки хеша пароля и механизм сессий для поддержания состояния аутентификации.
// config.php
$host = 'localhost';
$db = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
die('Подключение не удалось: ' . $e->getMessage());
}
App path php (работа с путями файлов в php)
// login.php
session_start();
require 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$stmt = $pdo->prepare('SELECT id, password FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
header('Location: dashboard.php');
exit;
} else {
$error = 'Неверный email или пароль';
}
}
App php domain (работа с доменами в php)
Возможные проблемы:
- Отсутствие проверки входящих данных может привести к XSS-атакам. Рекомендуется фильтровать и экранировать вывод.
- Слабая обработка ошибок PDO: исключение может раскрыть структуру БД. В production используйте логирование.
- Не забывайте вызывать session_start() перед любым выводом.
Как сделать вход с использованием MySQLi вместо PDO?
MySQLi также позволяет выполнять подготовленные запросы, но его API менее универсален, чем PDO. Решение подходит для проектов, уже использующих MySQLi.
$mysqli = new mysqli('localhost', 'root', '', 'test');
if ($mysqli->connect_error) {
die('Ошибка подключения: ' . $mysqli->connect_error);
}
$email = $_POST['email'];
$password = $_POST['password'];
$stmt = $mysqli->prepare('SELECT id, password FROM users WHERE email = ?');
$stmt->bind_param('s', $email);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
// перенаправление
}
Http user agent php (получение user-agent в php)
Типичная ошибка: забыть вызвать bind_param с правильными типами. Это приводит к непредсказуемому поведению запроса.
Как реализовать вход с устаревшим хешированием (md5)?
Этот вариант рассматривается только для обратной совместимости старых систем. Использование md5 или sha1 без соли крайне небезопасно.
// Пример (не рекомендуется)
$hash = md5($password);
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ? AND password = ?');
$stmt->execute([$email, $hash]);
Config app php (конфигурация php приложения)
Проблема: пароли хранятся в открытом виде или легко поддаются радужным таблицам. Для миграции можно использовать двойное хеширование с последующим пересозданием хеша при входе.
Как внедрить вход с помощью JWT для stateless API?
JWT подходит для REST API и микросервисной архитектуры, где сервер не хранит сессии. После аутентификации клиент получает токен, который прикрепляется к каждому запросу.
// Генерация JWT после успешного входа
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
$key = 'secret_key';
$payload = [
'user_id' => $user['id'],
'exp' => time() + 3600
];
$jwt = JWT::encode($payload, $key, 'HS256');
echo json_encode(['token' => $jwt]);
Ошибки: использование слабого ключа, отсутствие проверки срока действия токена, уязвимость к атакам с повторным использованием (replay). Рекомендуется хранить ключ вне кода и использовать HTTPS.
Расширенные примеры и пояснения
Ниже приведены подробные примеры для углубленного понимания и защиты системы входа.
Полный скрипт login.php с защитой от CSRF и ограничением попыток
Этот пример включает генерацию CSRF-токена, проверку количества неудачных попыток и установку сессии.
// csrf_token.php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// login.php (часть кода)
require 'csrf_token.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Проверка CSRF
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
die('Недействительный CSRF-токен');
}
// Ограничение попыток (в сессии)
$maxAttempts = 5;
$lockoutTime = 300; // 5 минут
if (isset($_SESSION['login_attempts']) && $_SESSION['login_attempts'] >= $maxAttempts) {
$remaining = $lockoutTime - (time() - $_SESSION['last_attempt_time']);
if ($remaining > 0) {
die('Слишком много попыток. Попробуйте через ' . ceil($remaining / 60) . ' мин.');
} else {
$_SESSION['login_attempts'] = 0;
}
}
// Логика аутентификации
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
if (!$email) {
$error = 'Некорректный email';
} else {
// Проверка пароля...
if ($user && password_verify($password, $user['password'])) {
// Успех: сброс попыток
unset($_SESSION['login_attempts']);
unset($_SESSION['last_attempt_time']);
$_SESSION['user_id'] = $user['id'];
header('Location: dashboard.php');
exit;
} else {
$_SESSION['login_attempts'] = ($_SESSION['login_attempts'] ?? 0) + 1;
$_SESSION['last_attempt_time'] = time();
$error = 'Неверные учетные данные';
}
}
}
?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="email" name="email" required>
<input type="password" name="password" required>
<button type="submit">Войти</button>
</form>
// Результат: после пяти неверных попыток в течение 5 минут пользователь блокируется.
Реализация Remember Me (запоминание на устройстве)
Добавляет долгоживущую cookie для автоматического входа. Используется токен, хранящийся в базе.
// При успешном входе с опцией "запомнить"
if (isset($_POST['remember'])) {
$token = bin2hex(random_bytes(64));
$hashedToken = password_hash($token, PASSWORD_DEFAULT);
// Сохраняем хеш токена в БД вместе с user_id и меткой времени
$stmt = $pdo->prepare('INSERT INTO user_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)');
$stmt->execute([$user['id'], $hashedToken, date('Y-m-d H:i:s', time() + 30*24*3600)]);
setcookie('remember_token', $token, time() + 30*24*3600, '/', '', true, true);
}
// На странице проверки (init.php)
if (!isset($_SESSION['user_id']) && isset($_COOKIE['remember_token'])) {
$token = $_COOKIE['remember_token'];
$stmt = $pdo->prepare('SELECT user_id, token_hash FROM user_tokens WHERE expires_at > NOW()');
$stmt->execute();
$records = $stmt->fetchAll();
foreach ($records as $record) {
if (password_verify($token, $record['token_hash'])) {
$_SESSION['user_id'] = $record['user_id'];
// Обновляем токен для предотвращения фиксации
break;
}
}
}
// Результат: пользователь автоматически входит до истечения срока токена (30 дней).
Аутентификация через OAuth (пример с Google)
Требуется библиотека Google API Client и настройка OAuth 2.0 в Google Cloud Console.
require_once __DIR__ . '/vendor/autoload.php';
$client = new Google\Client();
$client->setClientId('YOUR_CLIENT_ID');
$client->setClientSecret('YOUR_CLIENT_SECRET');
$client->setRedirectUri('http://localhost/google-callback.php');
$client->addScope('email');
// Ссылка для входа
$authUrl = $client->createAuthUrl();
echo '<a href="' . htmlspecialchars($authUrl) . '">Войти через Google</a>';
// Обработка callback (google-callback.php)
if (isset($_GET['code'])) {
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
$client->setAccessToken($token);
$oauth2 = new Google\Service\Oauth2($client);
$userinfo = $oauth2->userinfo->get();
// Получаем email, name и т.д.
$email = $userinfo->email;
// Ищем пользователя в своей БД или создаем нового
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
if (!$user) {
$stmt = $pdo->prepare('INSERT INTO users (email, name) VALUES (?, ?)');
$stmt->execute([$email, $userinfo->name]);
$userId = $pdo->lastInsertId();
} else {
$userId = $user['id'];
}
$_SESSION['user_id'] = $userId;
header('Location: dashboard.php');
}
// Результат: пользователь перенаправляется на страницу входа Google, после подтверждения возвращается на сайт уже авторизованным.