Basic Auth - proste zabezpieczenie zasobów WWW

Każdy z nas posiada jakieś usługi lub zasoby dostępne przez WWW których nie chce udostępniać publicznie, administratorzy mogą mieć statystyki, systemy monitorujące, systemy wspomagające zarządzanie codziennymi procesami, a użytkownicy np. katalogi z plikami. Jeśli zastanawiasz się, czy jest jakiś prosty sposób zabezpieczenia dostępu do konkretnych zasobów, to odpowiem Ci, że jest - autoryzacja typu Basic Auth. ;-)

Czym jest Basic Auth i jak działa?

Basic auth jest autoryzacją realizowaną na warstwie protokołu HTTP np. przez przeglądarkę WWW (klienta HTTP). Schemat działania jest następujący:

  • klient wysyła żądanie o zasób do serwera WWW,
  • serwer odpowiada komunikatem z kodem HTTP 401 (Authorization Required),
  • przeglądarka interpretuje kod 401 i wyświetla monit o podanie loginu i hasła,
  • użytkownik wpisuje login i hasło
  • przeglądarka wysyła ponownie żądanie o zasób, ale dodaje nagłówek "Authorization" z wartością zbudowaną na podstawie loginu i hasła - base64(login:hasło)
  • serwer weryfikuje użytkownika sprawdzając nagłówek "Authorization"

Prześledźmy najlepiej całą komunikację pomiędzy klientem a serwerem żeby dokładnie zrozumieć co się dzieje.

→ Wysyłamy żądanie o stronę główną do serwera:

# printf "GET / HTTP/1.0\r\n\r\n"|nc 192.168.1.232 80
HTTP/1.1 401 Authorization Required
Date: Thu, 03 Mar 2011 21:02:14 GMT
Server: Apache/2.2.16 (Debian)
WWW-Authenticate: Basic realm="Autoryzacja"
Vary: Accept-Encoding
Content-Length: 475
Connection: close
Content-Type: text/html; charset=iso-8859-1

← Serwer zwrócił nam kod HTTP 401 i żąda autoryzacji (w tym momencie przeglądarka wyświetla monit login/hasło), wygenerujmy string zawierający nasz login i hasło który prześlemy do serwera:

# echo -ne "jamzed:trudne"|base64
amFtemVkOnRydWRuZQ==

czyli:

login: jamzed
hasło: trudne

→ Wyślijmy teraz te same żądanie, ale już z nagłówkiem Authorization: Basic $string:

# printf "GET / HTTP/1.0\r\nAuthorization: Basic amFtemVkOnRydWRuZQ==\r\n\r\n"|nc 192.168.1.232 80
HTTP/1.1 200 OK
Date: Thu, 03 Mar 2011 21:32:06 GMT
Server: Apache/2.2.16 (Debian)
Vary: Accept-Encoding
Content-Length: 524
Connection: close
Content-Type: text/html;charset=UTF-8

← Otrzymujemy 200 OK ;-) serwer przyjął nasz login/hasło i zwrócił nam stronę.

Jak można włączyć autoryzację Basic?

Metod jest kilka, w zależności od serwera oraz sposobu przechowywania haseł, ja przedstawię przykładową konfigurację dla serwera Apache na dwóch przykładach:

  1. plik .htaccess i plik .htpasswd z hasłami
  2. plik .htaccess i hasła przechowywane w bazie danych MySQL

Nic nie stoi na przeszkodzie, żeby np. PHP realizował autoryzację Basic Auth, wystarczy, że skrypt wyśle odpowiednie nagłówki do klienta (401), a następnie będzie sprawdzał czy klient wysyła poprawny nagłówek Authorization: Basic.

1. htaccess i htpasswd

Jest to najprostszy i chyba najpopularniejszy sposób uruchomienia autoryzacji, nasz serwer musi mieć załadowany moduł auth_basic oraz mieć właczoną obsługę dynamicznej zmiany konfiguracji (dyrektywa AllowOverride), która domyślnie znajduje się w pliku .htaccess, żeby zezwolić na zmianę konfiguracji serwera poprzez plik, musimy dodać do definicji naszego vhosta:

<Directory /var/www/>
AllowOverride AuthConfig
</Directory>

/var/www/ jest katalogiem w którym zezwalamy na używanie .htaccess, automatycznie wszystkie katalogu poniżej /var/www/ dziedziczą tą opcję.

Zawartość pliku .htaccess:

AuthName "Dostęp wymaga autoryzacji"
AuthUserFile /var/www/.htpasswd
AuthType basic
Require valid-user

AuthName - komunikat wyświetlony w przeglądarce
AuthUserFile - plik zawierający dane autentykacyjne użytkowników
AuthType - rodzaj autoryzacji
Require - co jest wymagane by zautoryzować użytkownika, w tym przypadku poprawny login i hasło, ale może być też konfiguracja z dodatkowymi opcjami w postaci grup użytkowników

Stwórzmy teraz plik .htpasswd i dodajmy użytkownika 'jamzed':

# htpasswd -c .htpasswd jamzed
New password:
Re-type new password:
Adding password for user jamzed

Narzędzie htpasswd pozwala nam utworzyć plik i umieścić w nim użytkowników (-c oznacza utworzenie nowego pliku), jeśli plik już istnieje wywołujemy:

# htpasswd .htpasswd nazwa_użytkownika

Jeśli wszystko skonfigurowaliśmy poprawnie, podczas próby pobrania strony powinniśmy zostać poproszeni o hasło.

2. htaccess + hasła w bazie MySQL

Centralizacja systemu autentykacji poprzez przechowywanie użytkowników w bazie danych potrafi bardzo uprościć zarządzanie dostępem. Pierwsza konfiguracja wymaga stworzenia bazy danych, ale myślę, że warto poświęcić czas na to i później mieć pełną kontrolę, gdzie, kto i jaki ma dostęp.

Zakładam, że AllowOverride mamy włączone ;-) pozostaje nam stworzenie struktury bazy danych oraz uruchomienie modułu auth_mysql, jeśli nie posiadamy takiego w systemie to musimy go dokompilować lub zainstalować, w moim przypadku środowiskiem jest Debian 6.0, więc instalacja bazy danych oraz modułu sprowadza się do wykonania polecenia:

# apt-get install libapache2-mod-auth-mysql mysql-server

Włączenie modułu:

# a2enmod

wpisujemy auth_mysql i zatwierdzamy

Enabling module auth_mysql.
Run '/etc/init.d/apache2 restart' to activate new configuration!

Restart Apache i mamy wszystko gotowe. Stwórzmy strukturę bazy danych:

mysql> create database authorize;
mysql> use authorize;
mysql> create table users (login varchar(25) not null primary key default '', password varchar(40) not null default '', groups varchar(25) not null default '');

Hasła użytkowników w bazie danych będziemy przechowywać w postaci SHA1, dodajemy użytkownika:

mysql> insert into users values('jamzed',sha1('trudne'),'');

Nadajemy uprawnienia modułowi autoryzacyjnemu:

mysql> grant SELECT on authorize.users to 'auth'@'localhost' identified by 'haslo';

Czyli mamy już strukturę bazy danych, testowego użytkownika i niezbędne uprawnienia aby Apache mógł się połączyć z bazą i dokonać weryfikacji.

Konfiguracja .htaccess:

AuthBasicAuthoritative Off
AuthUserFile /dev/null
AuthMySQL On
AuthMySQL_Host localhost
AuthMySQL_User auth
AuthMySQL_Password haslo
AuthMySQL_DB authorize
AuthMySQL_Password_Table users
AuthMySQL_Username_Field login
AuthMySQL_Password_Field password
AuthMySQL_Empty_Passwords Off
AuthMySQL_Encryption_Types SHA1Sum
AuthMySQL_Authoritative On

AuthType Basic
AuthName "Autoryzacja"
Require valid-user

AuthBasicAuthoritative - zezwala nam na wykorzystanie innych modułów do autoryzacji, w tym przypadku auth_mysql
AuthUserFile - musimy podać plik z danymi użytkowników, my korzystamy z bazy danych, więc podajemy /dev/null
AuthMySQL On - włączenie modułu autoryzacji z bazy MySQL
AuthMySQL_Host - host na którym znajduje się baza danych (localhost)
AuthMySQL_User - użytkownik do połączenia się z bazą danych (auth)
AuthMySQL_Password - hasło do połączenia się z bazą danych (haslo)
AuthMySQL_DB - nazwa bazy danych w której znajduje się tabela z użytkownikami (authorize)
AuthMySQL_Password_Table - tabela z użytkownika (users)
AuthMySQL_Username_Field - nazwa pola z loginem
AuthMySQL_Password_Field - nazwa pola z hasłem
AuthMySQL_Empty_Passwords - czy zezwalamy na puste hasła?
AuthMySQL_Encryption_Types - sposób przechowywania haseł użytkowników w bazie danych (sha1), dlaczego nie md5?
AuthMySQL_Authoritative - czy jest to nasz pożądany sposób autoryzacji?
AuthType, AuthName i Require - analogicznie do powyższego przykładu z plikiem .htpasswd

To powinno nam wystarczyć, aby rozpocząć autoryzację użytkowników z bazy danych, w przypadku wystąpienia problemów z konfiguracją, serwer zwróci komunikat z błędem 500, wtedy warto przejrzeć nasz error_log, komunikaty wysyłane przez moduł auth_mysql są raczej czytelne, najczęściej problem związany jest z brakiem uprawnień do odczytu z bazy danych lub źle podanej nazwy formatu w którym przechowujemy hasła.

  • Wystarczy samo "AllowOverride AuthConfig" zamiast "All" - po co dawać za dużo? ;)

  • Słuszna uwaga @drozdo ;-) poprawione, dzięki ;-)

  • Jeszcze jedna uwaga: auth_mysql obsługuje solenie haseł - możnaby od razu best practices pokazać ;-)

  • chociaż w sumie to best practices nie używałoby basica - no ale tak czy owak przyjemnie się czyta varloga - oby tak dalej ;)

  • pluto

    A da się zamiast mysql-a użyć zwyklego systemowego konta do autoryzacji?
    jaka jest różnica w wydajności loginu linuksowego vs mysql albo htaccess? da się/opłaca tak odciążyć bazę danych? zamiast zapisywać w niej hasła i loginy userów tworzyć konta systemowe (dla systemu rozproszonego jakiś nis albo ldap), czy takie podejście nie upraszcza znacząco autoryzacji np. w systemach w których klient ma konto pocztowe czy coś tam innego ?

  • Pingback: Interfejs » 11. Free File Sync()

  • Daniel

    Co w takim razie jest best practice w tym względzie? htdigest?

  • pnti

    Mam wrażenie, że .htpasswd nie powinno być wystawione na widok publiczny...

  • pnti

    I jeszcze złe cudzysłowy w .htaccess :)