Блог


Базовая аутентификция.

Статья в рамках программы переноса материалов со старого сайта.

Базовая аутентификация - простейший вид защиты каталогов на сайте. Но не смотря на её простоту, она достаточно надежна для того, чтобы спать спокойно, не опасаясь за сокровища, которые хранятся на сайте. Правда есть одна тонкость. Если PHP стоит не как модуль, а как CGI, то такая аутентификация работать не будет. Увы.

Давайте рассмотрим, как это можно сделать.

Вариант 1. Защита подключаемым файлом
Раз мы не используем базу данных, то значит аутентификационные данные будут хранится в файле. Чтобы они не стали достоянием общественности, файл должен быть исполняемым, то есть иметь расширение .php Очень удобно хранить данные в виде массива, потому что сразу видна принадлежность пароля к логину. Пароль от греха лучше захэшировать, хуже от этого точно не станет. Чтобы это сделать, можно просто выполнить такой код:

1
 2
 3
 4
 5
 6
<?php


    
echo md5('тут_пароль');

Ну или использовать другой алгоритм хэшировния. Сейчас важно не это, важен сам принцип аутентификации. То, что получится, и есть хэш.

Теперь собственно сам массив. Можно в него добавлять кучу данных, по числу доверенных лиц.
Логин => хэш пароля.

1
 2
 3
 4
 5
 6
 7
 8
 9
 10
<?php

    $admins 
= array(  
               
'root'   => '63a9f0ea7bb98050796b649e85481845'// root => root  
               
'admin'  => 'e10adc3949ba59abbe56e057f20f883e',  
              );

Положим этот массив в файл auth.php


Теперь приступим, собственно, к созданию файла, который будет отвечать за аутентификацию. Для этого вида авторизации нужно отослать браузеру клиента такие заголовки:

1
 2
 3
  WWW-Authenticate: Basic realm=\"Подпись на окне\" 
  HTTP/1.0 401 Unauthorised

Браузер выдаст окошко для ввода логина и пароля. Введённые данные будут помещены в суперглобальный массив: логин в элемент суперглобального массива
$_SERVER['PHP_AUTH_USER'],
а пароль - в
$_SERVER['PHP_AUTH_PW'].

Теперь можно посмотреть это в деле. Тоесть, если пользователь не авторизован, то  наш сценарий посылает ему запрос на авторизацию:

1
 2
 3
 4
 5
 6
 7
 8
 9
 10
<?php

    
if(empty($_SERVER['PHP_AUTH_USER'])) 
    { 
        
header('WWW-Authenticate: Basic realm="Administrative resource"'); 
       
header("HTTP/1.0 401 Unauthorised"); 
        exit();  
    }

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

1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
<?php
// Подключаем файл с аутентификационными данными 
     
include './auth.php'

// Устанавливаем ключ входа  
     
$key false;  
// Сверем данные аутентификации  с полученными от юзера      
     
if(isset($admins[$_SERVER['PHP_AUTH_USER']]) &&
       
md5($_SERVER['PHP_AUTH_PW']) === $admins[$_SERVER['PHP_AUTH_USER']])    
         
$key true;// Если пара совпала, переустанавливаем ключ




Ну а что бы не посылать сразу лесом, если человек ошибся, выдадим ему окошко еще раз.

1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
<?php
// Если ключа так и нет, опять окно и стоп-машина. 
    
if(empty($key))      
    { 
       
// опять выдаем юзеру это окошко, что бы заполнил                 
        
header('WWW-Authenticate: Basic realm="Administrative resource"');      
       
header ('HTTP/1.0 401 Unauthorized');      
        exit();
    }      
 


Ну и соберем теперь все это в кучу:

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
<?php

    
if(empty($_SERVER['PHP_AUTH_USER']))  
    {  
        
header('WWW-Authenticate: Basic realm="Administrative resource"');  
        
header("HTTP/1.0 401 Unauthorised");  
        exit();   
    } 

    include 
'./auth.php'
    
$key false;  
      
    if(isset(
$admins[$_SERVER['PHP_AUTH_USER']]) &&
       
md5($_SERVER['PHP_AUTH_PW']) === $admins[$_SERVER['PHP_AUTH_USER']])    
         
$key true;

    if(empty(
$key))      
    {              
        
header('WWW-Authenticate: Basic realm="Administrative resource"');
        
header ('HTTP/1.0 401 Unauthorized');      
        exit();      
    
    } 


Теперь стоит поместить эти строки в начала любого файла и доступ к нему будет ограничен узкому кругму лиц.

Вариант 2. Защита подключаемым файлом с использованием БД

Она почти не отличается от той, которую мы только что рассмотрели. Разница в том, что данные будут хранится не в файле, а в базе данных

Первым делом нужно создать MySQL-таблицу, где будут храниться логины админов и их пароли. Таблица будет такая:

1
 2
 3
 4
 5
 6
 CREATE TABLE `admin` ( 
`id` INT(11) NOT NULL AUTO_INCREMENT, 
`user` VARCHAR(50) NOT NULL, 
`password` TINYTEXT NOT NULL, 
PRIMARY KEY (`id`)  
);

Теперь занесём данные в таблицу (пароль шифруем так же, как и раньше):

1
INSERT INTO `admin` ( `id` , `user` , `password` ) 
VALUES ('', 'admin', '21232f297a57a5a743894a0e4a801fc3');


При желании в дальнейшем можно будет добавить ещё пользователей. Но пока только один. 'admin' => 'admin'
Теперь выведем в нашей программе запрос к базе данных при введении логина и пароля, экранируя специальные символы для этого запроса при вводе логина. Пароль экранировать не нужно, функция md5() (или любя другая функция хэшировния пароля) не пустит ничего лишнего.
Нужно помнить о магических кавычках, иначе будет двойное экранирование.

А если при выборке в таблице не будет обнаружено записи по заданным условиям, то снова покажем окно для авторизации:

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
<?php

// Коннект лучше использовть общий для сайта
    
$link mysqli_connect('localhost''root''''DBname'
                           or die(
'No connect'); 
  
    
mysqli_set_charset($link'utf8');
/////////////////////////////////////////////////// 
  
    
if(empty($_SERVER['PHP_AUTH_USER'])) 
    { 
        
header('WWW-Authenticate: Basic realm="Administrative resource"'); 
        
header("HTTP/1.0 401 Unauthorised"); 
        exit();  
    } 

    
$res mysqli_query($link,
             
"SELECT COUNT(*) AS `cnt` FROM `admin`  
              WHERE `user` = '"
mysqli_real_escape_string($link
                                       $_SERVER['PHP_AUTH_USER']) ."'  
              AND   `password` = '"
md5($_SERVER['PHP_AUTH_PW']) ."'" 
                         
); 
 
    
$data mysqli_fetch_assoc($res);

    if (empty(
$data['cnt'])
    { 
        
header('WWW-Authenticate: Basic realm="Administrative resource"'); 
        
header ("HTTP/1.0 401 Unauthorized"); 
        exit(); 
    } 


Ну а дальше все точно так же, пишем это первыми строками и спим спокойно.

Вариант 3. Защита при помощи .htaccess и .htpasswd.

В данном случае нужно создать два файла: один .htaccess, другой - с логинами и паролями. Первый файл положим в директорию, доступ к которой нужно ограничить.

Что в нем? 

Строчка "AuthType Basic" будет указывать метод шифровки пароля.

"AuthName "Надпись в окошке" " - здесь просто указывается, какая надпись будет выдана в форме.

"AuthUserFile /путь к файлу/.htpasswd" - путь к файлу с паролями. Общепринято называть такие файлы .htpasswd (хотя это и не обязательно).

"require valid-user" - эта строка сообщает, что доступ к директории будет открыт всем пользователям, чьи логины и пароли есть в .htpasswd-файле. Чтобы, к примеру, дать доступ конкретным пользователям (например, Admin), нужно написать "Require user Admin" (остальных, если потребуется, можно указать через пробел).

Итак, у нас получился .htaccess-файл такого вида:

1
 2
 3
 4
AuthType Basic 
AuthName "Administrative resource" 
AuthUserFile /путь к файлу/.htpasswd 
require valid-user

Теперь нужно создать файл с паролями. Создаётся он при помощи утилиты htpasswd (файл htpasswd.exe), входящей в состав сервера Apache и лежащей в папке bin.

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

Примечание. 
Если у Вас стоит пакет Денвер, то в нем нет такой утилиты. 
Выход прост. Берем её здесь, рспаковываем и складываем по пути 
Z:\usr\local\apache\ 
Теперь все готово.

В Windows можно добраться до htpasswd, используя, например, Total или Windows Commander.

Нужно перейти в директорию, где хранится утилита, и набрать в командной строке это:

1
htpasswd -cm .htpasswd admin




Жмем "enter", получаем такое окошко:



Вводим и повторяем пароль и жмем ввод.
Всё, файл создан.



Это будет текстовой файл внутри которого храниться логин и через двоеточие зашифрованный пароль. Вот так:

1
admin:$apr1$qf3.....$2xWCAZSUuAVXrnsnW4D071


Если встанет необходимость добавить в файл ещё логины с паролями, то надо проделать ту же самую процедуру, но из команды на создание нового логина просто убрать ключ "с". Вот так:

1
htpasswd -m .htpasswd admin2


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

1
 2
 3
 4
AuthType Basic 
AuthName "Administrative resource" 
AuthUserFile /путь к файлу/.htpasswd 
require valid-user


Место для этого файла лучше выбирать выше корневой директории, куда нет доступа по протоколу HTTP. Но если нет такой возможности, то лучше положить его в отдельную папку и защитить тем же самым сервером, положив в неё .htaccess такого содержания:

1
 2
 3
<Files .htpasswd>  
   deny from all  
</Files>


Ну вот и все, пробуйте. И спокойного, крепкого сна :)

Винокуров Александр aka A.V.

Теги: PHP | MySQL

Комментарии (11)

Николай
11-02-2014

Спасибо за Вашу статью. делать все нужно не правильно, а точно и в правильной последовательности. Если программист цикл с пред условием при конкретном значении счетчика он должен так обкуриться и обдолбиться что себя в зеркале неузновал бы, это с прошлой вашей статьи там где вы рассматривали уроки попова.
Сергей
11-04-2014
Во первых спасибо автору. У меня вопрос по второму варианту. Если сделать как в примере, работает нормально. Но я хочу подключить свой файл конекта с базой

include './blocks/bd.php';
в нем:
$link = mysql_connect ("localhost","root","");
mysql_select_db ("soft-main",$db);
mysql_query("SET NAMES 'cp1251'");

вместо

$link = mysqli_connect('localhost', 'root', '', 'soft-main')
or die('No connect');

Но мой вариант не работает, как лучше сделать?
Сергей
11-04-2014
Ой.. зачем давать возможность пользователем выбирать цвет сообщения, получилось плохо

Во первых спасибо автору. У меня вопрос по второму варианту. Если сделать как в примере, работает нормально. Но я хочу подключить свой файл конекта с базой

include './blocks/bd.php';
в нем:
1
2
3
4
5

$link = mysql_connect ("localhost","root","");
mysql_select_db ("soft-main",$db);
mysql_query("SET NAMES 'cp1251'");



вместо

1
2
3
4

$link = mysqli_connect('localhost', 'root', '', 'soft-main') 
or die('No connect');



Но мой вариант не работает, как лучше сделать?
Otto
02-06-2014
Ваша ошибка тут
mysql_select_db ("soft-main",$db);

Эта строка в Вашем случае должна выглядеть так
mysql_select_db ("soft-main",$link);

А вообще смотрите синтаксис команд и функций.
Сергей
22-12-2014
С первым и третим примером ничего не получилось.
В первом ошибка Cannot modify header information
В третьем тоже какие-то ошибки.
Возможно сделал что-то не так.
twin
22-12-2014
В первом ошибка связана с выводом до отправки заголовка. До функции header() не должно быть никакого HTML, вывода и даже пробелов. Весьма вероятно, что есть BOM.
В третьем наверное действительно сделали что то не то, по крайней мере не понятно, какие ошибки.
Сергей
22-12-2014
Спасибо, с первым вариантом, дело было действительно с BOM, с помощью нотпада перекодирывал с без BOM и всё заработало. Но работает только на денвере, заливаю на хост, ввожу логин и пароль, результата нет. Скорее всего на хосте отключены глобальные переменные

А с третьим ошибка 500 Internal Server Error , тут дело скорее всего в настройках хостинга
Нурик
17-03-2015

У меня не получается!!! Помогите!!!


Я пробовал первые две варианты. У обоих после ввода логина и пароля обратно выдает ту же форму заполнения! Как исправит???
twin
20-03-2015
Задайте вопрос на нашем форуме. Здесь в комментах не совсем удобно разбирать коды.
Александр
16-07-2015
А как часто при такой авторизации будет повторяться проверка логина и пароля?
И подскажите, как можно прописать, чтобы окно авторизации выскакивало либо с определённой периодичностью, либо когда пользователь не проводит никаких операции длительное время?
twin
16-07-2015
Каждый раз после закрытия окна.

По второму вопросу - боюсь никак. Это окно генерирует браузер, и ни js ни тем более с серверной стороны на это повлиять сложно.

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

 
Наверх