Аутентификация через HTTP протокол в PHP скриптах
Основные способы аутентификации через HTTP в PHP
Как реализовать базовую HTTP-аутентификацию с помощью PHP наиболее эффективно?
Наиболее эффективное решение - использование встроенной поддержки HTTP Basic Auth в PHP. Оно не требует внешних библиотек и легко защищает отдельные страницы или целые директории. Сервер отправляет заголовок WWW-Authenticate, браузер показывает диалог ввода логина и пароля, а PHP получает данные через суперглобальные переменные $_SERVER['PHP_AUTH_USER'] и $_SERVER['PHP_AUTH_PW'].
<?php
// Простейшая проверка логина и пароля
$valid_user = 'admin';
$valid_pass = 'secret123';
if (!isset($_SERVER['PHP_AUTH_USER']) ||
$_SERVER['PHP_AUTH_USER'] !== $valid_user ||
$_SERVER['PHP_AUTH_PW'] !== $valid_pass) {
header('WWW-Authenticate: Basic realm="Private Area"');
header('HTTP/1.0 401 Unauthorized');
echo 'Доступ запрещен';
exit;
}
?>
<!DOCTYPE html>
<html><body><p>Привет, <?= htmlspecialchars($_SERVER['PHP_AUTH_USER']) ?>!</p></body></html>
Типичные проблемы и их решения
- Заголовки не отправляются, если перед ними есть вывод - убедитесь, что перед вызовом
header()нет никакого вывода (echo, пробелов вне <?php ... ?>). - PHP_AUTH_USER не заполнен на CGI/FastCGI - добавьте в .htaccess:
RewriteEngine On RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]. - Пароли передаются в открытом виде - используйте HTTPS, иначе пароль можно перехватить.
Как настроить аутентификацию на уровне веб-сервера (Apache) без кода PHP?
Этот подход полностью реализуется в конфигурации сервера. PHP не участвует в проверке, но может получить имя пользователя через переменную окружения $_SERVER['REMOTE_USER'].
# Файл .htaccess в защищаемой директории
AuthType Basic
AuthName "Доступ только для сотрудников"
AuthUserFile /var/www/.htpasswd
Require valid-user
Файл .htpasswd создается утилитой htpasswd:
htpasswd -c /var/www/.htpasswd user1
# Будет запрошен пароль
Ошибки при использовании .htaccess
- 500 Internal Server Error - проверьте синтаксис директив, доступность файла паролей.
- Имя пользователя не отображается в PHP - директива
AuthType Basicпередает переменную$_SERVER['REMOTE_USER'], но неPHP_AUTH_USER.
Как реализовать HTTP Digest-аутентификацию в PHP?
Digest-аутентификация более безопасна, так как пароль не передается в открытом виде, а используется хеш MD5. Реализация сложнее, но возможна без внешних модулей.
<?php
$realm = 'Защищенная зона';
$users = ['admin' => '5ebe2294ecd0e0f08eab7690d2a6ee69']; // md5('admin:secret')
if (!isset($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
die('Требуется аутентификация');
}
// Разбор строки авторизации
$data = parseDigest($_SERVER['PHP_AUTH_DIGEST']);
if (!$data || !isset($users[$data['username']])) {
die('Неверные учетные данные');
}
$A1 = $users[$data['username']];
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] !== $valid_response) {
die('Неверный пароль');
}
echo 'Привет, '.htmlspecialchars($data['username']);
function parseDigest($txt) {
$needed_parts = ['username','realm','nonce','uri','response','opaque','qop','nc','cnonce'];
$data = [];
preg_match_all('/(\w+)=["\']?([^"\',]+)["\']?/', $txt, $matches);
foreach ($matches[1] as $i => $key) {
$data[$key] = $matches[2][$i];
}
return $data;
}
?>
Проблемы Digest-аутентификации
- Некоторые браузеры не поддерживают Digest - проверьте совместимость.
- Сложность реализации - легко допустить ошибку в хешировании, пароль хранится в открытом виде (или MD5).
Как использовать cURL в PHP для отправки HTTP-запроса с аутентификацией?
Это вариант, когда PHP выступает клиентом и должен передать учетные данные на внешний сервер с HTTP Auth.
<?php
$url = 'https://api.example.com/data';
$username = 'apiuser';
$password = 'apikey';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
Ошибки при работе cURL
- Ошибка 401 - проверьте правильность логина/пароля и метод аутентификации (Basic/Digest).
- SSL-ошибки - добавьте
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);для тестов (не для продакшена).
Расширенные примеры реализации HTTP-аутентификации в PHP
Пример 1: Проверка логина и пароля из базы данных MySQL
Вместо жёстко заданных логинов используется таблица users с хешированными паролями (bcrypt).
<?php
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$login = $_SERVER['PHP_AUTH_USER'] ?? '';
$pass = $_SERVER['PHP_AUTH_PW'] ?? '';
$stmt = $db->prepare('SELECT password_hash FROM users WHERE login = ?');
$stmt->execute([$login]);
$row = $stmt->fetch();
if (!$row || !password_verify($pass, $row['password_hash'])) {
header('WWW-Authenticate: Basic realm="Admin"');
header('HTTP/1.0 401 Unauthorized');
echo 'Неверный логин или пароль';
exit;
}
// успешная аутентификация
echo 'Добро пожаловать, ' . htmlspecialchars($login);
?>
Результат: при вводе неверных данных возвращается 401 и диалог аутентификации, при верных - приветствие.
Пример 2: Комбинированная аутентификация (Basic + сессия)
После первого успешного входа создается сессия, чтобы не запрашивать пароль при каждом запросе.
<?php
session_start();
if (isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) {
// уже аутентифицирован
} else {
// проверка Basic Auth
if (!isset($_SERVER['PHP_AUTH_USER']) || !verify_user($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
header('WWW-Authenticate: Basic realm="My App"');
header('HTTP/1.0 401 Unauthorized');
echo 'Доступ запрещён';
exit;
}
$_SESSION['authenticated'] = true;
$_SESSION['user'] = $_SERVER['PHP_AUTH_USER'];
}
echo 'Привет, ' . $_SESSION['user'];
?>
Пример 3: Аутентификация через Digest с использованием файла паролей .htdigest
Создание файла паролей:
htdigest -c /var/www/.htdigest "MyRealm" user1
Чтение и проверка в PHP:
<?php
$realm = 'MyRealm';
$htdigest_file = '/var/www/.htdigest';
function getDigestPassword($user, $realm, $file) {
$lines = file($file, FILE_IGNORE_NEW_LINES);
foreach ($lines as $line) {
list($u, $r, $hash) = explode(':', $line);
if ($u === $user && $r === $realm) return $hash;
}
return null;
}
if (!isset($_SERVER['PHP_AUTH_DIGEST'])) {
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
header('HTTP/1.1 401 Unauthorized');
exit;
}
$data = parseDigest($_SERVER['PHP_AUTH_DIGEST']);
$stored_hash = getDigestPassword($data['username'], $realm, $htdigest_file);
if (!$stored_hash) die('Пользователь не найден');
$A1 = $stored_hash;
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] !== $valid) die('Неверный пароль');
echo 'Доступ разрешён для '.htmlspecialchars($data['username']);
?>
Пример 4: Аутентификация через HTTP с помощью Stream Context (без cURL)
<?php
$url = 'https://api.example.com/data';
$auth = base64_encode('user:password');
$opts = [
'http' => [
'header' => "Authorization: Basic $auth"
]
];
$context = stream_context_create($opts);
$result = file_get_contents($url, false, $context);
if ($result === false) {
echo 'Ошибка получения данных';
} else {
echo $result;
}
?>
Результат: запрос выполняется с базовой аутентификацией, ответ сервера выводится.