Forum und email
Uwierzytelnianie HTTP w PHP

Rozdział 34. Uwierzytelnianie HTTP w PHP

Uwierzytelnianie HTTP jest obsługiwana przez PHP tylko wtedy, gdy PHP pracuje jako moduł Apache'a, nie jest dostępna w trybie CGI. W skrypcie można użyć funkcji header() by wysłać do przeglądarki komunikat "Wymagana autoryzacja", co spowoduje wyświetlenie okienka z polami Użytkownik i Hasło. Po wypełnieniu przez użytkownika tych pól, URL zawierający skrypt PHP zostanie ponownie wywołany z ustawionymi predefiniowanymi zmiennymi PHP_AUTH_USER, PHP_AUTH_PW i AUTH_TYPE zawierającymi odpowiednio nazwę użytkownika, hasło i typ autoryzacji. Zmienne te będą dostępne w tablicach $_SERVER oraz $HTTP_SERVER_VARS. Obecnie obsługiwane są autoryzacje typu "Basic" i "Digest" (od PHP 5.1.0). Więcej informacji znajdziesz w opisie funkcji header().

PHP Version Note: Zmienne superglobalne, takie jak $_SERVER, udostępniono w PHP 4.1.0. $HTTP_SERVER_VARS są dostępne od PHP 3.

Przykładowy skrypt wymuszający autoryzację klienta:

Przykład 34-1. Uwierzytelnianie Basic HTTP

<?php
  
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    
header('WWW-Authenticate: Basic realm="My Realm"');
    
header('HTTP/1.0 401 Unauthorized');
    echo
'Tekst do wysłania, jeśli użytkownik wciśnie przycisk Anuluj';
    exit;
  } else {
    echo
"<p>Hej {$_SERVER['PHP_AUTH_USER']}.</p>";
    echo
"<p>Twoje hasło to {$_SERVER['PHP_AUTH_PW']}.</p>";
  }
?>

Przykład 34-2. Przykład uwierzytelnianie Digest HTTP

Ten przykład pokazuje jak zaimplementować proste umierzytelnianie Digest HTTP. Po więcej informacji przeczytaj RFC 2617.

<?php
$realm
= 'Zastrzeżona strefa';

//user => password
$users = array('admin' => 'mypass', 'guest' => 'guest');


if (empty(
$_SERVER['PHP_AUTH_DIGEST'])) {
    
header('HTTP/1.1 401 Unauthorized');
    
header('WWW-Authenticate: Digest realm="'.$realm.
           
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');

    die(
'Tekst do wysłania kiedy użytkownik kliknie klawisz anuluj');
}


// analiza zmiennej PHP_AUTH_DIGEST
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
    !isset(
$users[$data['username']]))
    die(
'Nieprawidłowe listy uwierzytelniające!');


// tworzenie prawidłowej odpowiedzi
$A1 = md5($data['username'] . ':' . $realm . ':' . $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(
'Nieprawidłowe listy uwierzytelniające!');

// ok, prawidłowa nazwa użytkownika i hasło
echo 'Jesteś zalogowany jako: ' . $data['username'];


// function to parse the http auth header
function http_digest_parse($txt)
{
    
// zabezpieczenie przeciwko brakującym informacjom
    
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
    
$data = array();

    
preg_match_all('@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@', $txt, $matches, PREG_SET_ORDER);

    foreach (
$matches as $m) {
        
$data[$m[1]] = $m[3];
        unset(
$needed_parts[$m[1]]);
    }

    return
$needed_parts ? false : $data;
}
?>

Kompatybilność: Należy uważać z linijkami dodawanymi do nagłówka HTTP. W celu zachowania maksymalnej zgodności ze wszystkimi klientami, słowo Basic powinno zaczynać się dużą literą "B", wartość realm powinna być otoczona cudzysłowami (nie apostrofami), i dokładnie jeden znak odstępu powinien poprzedzać kod 401 w linii HTTP/1.0 401. Parametry autoryzacyjne muszą być oddzielone przecinkami jak pokazano to w przykładzie digest powyżej.

Zamiast wyświetlać wartość PHP_AUTH_USER i PHP_AUTH_PW, jak to zrobiono w powyższym przykładzie, zechcesz zapewne sprawdzić poprawność nazwy użytkownika i hasła. Na przykład poprzez zapytanie do bazy danych lub odnalezienie użytkownika w pliku dbm.

Należy uważać na kapryśne przeglądarki Internet Explorer. Są wrażliwe na kolejność wysyłanych nagłówków HTTP. Wysłanie nagłowka WWW-Authenticate przed HTTP/1.0 401 powinno rozwiązać problem.

Aby zapobiec sytuacji w której ktoś napisze skrypt wykradający hasło wysłane tradycyjnym zewnętrznym mechanizmem, zmienne PHP_AUTH nie będą ustawiane, jeśli dla danej strony aktywna jest autoryzacja zewnętrzna i tryb bezpieczny jest włączony. Bez względu na to, REMOTE_USER może zostać użyte do zidentyfikowania użytkownika autoryzowanego zewnętrznie Zatem, możesz użyć $_SERVER['REMOTE_USER'].

Konfiguracja: Aby wykryć czy miała miejsce zewnętrzna autoryzacja, PHP sprwadza obecność dyrektywy AuthType.

Powyższa metoda nie zapobiega jednak wykradaniu haseł do stron wymagających autoryzacji przez kogoś, kto na tym samym serwerze kontroluje strony nie wymagające autoryzacji.

Zarówno Netscape Navigator jak i Internet Explorer opróżnią bufor autoryzacji po otrzymaniu od serwera kodu 401. Można w ten sposób wylogowanić użytkownika i zmusić go do ponownego wysłania nazwy użytkownika i hasła. Tej metody można użyć do wylogowania użytkownika po określonym czasie lub stworzenia przycisku "Wyloguj".

Przykład 34-3. Uwierzytelnianie HTTP z wymuszeniem przelogowania

<?php
  
function authenticate() {
    
header('WWW-Authenticate: Basic realm="Testowy system autoryzacji"');
    
header('HTTP/1.0 401 Unauthorized');
    echo
"Musisz podać poprawny login i hasło by wejść na tę stronę\n";
    exit;
  }

  if (!isset(
$_SERVER['PHP_AUTH_USER']) ||
    (
$_POST['SeenBefore'] == 1 && $_POST['OldAuth'] == $_SERVER['PHP_AUTH_USER'])) {
    
authenticate();
  } else {
    echo
"<p>Witaj: {$_SERVER['PHP_AUTH_USER']}<br />";
    echo
"Poprzednio: {$_REQUEST['OldAuth']}";
    echo
"<form action='{$_SERVER['PHP_SELF']}' METHOD='post'>\n";
    echo
"<input type='hidden' name='SeenBefore' value='1' />\n";
    echo
"<input type='hidden' name='OldAuth' value='{$_SERVER['PHP_AUTH_USER']}' />\n";
    echo
"<input type='submit' value='Re Authenticate' />\n";
    echo
"</form></p>\n";
}
?>

Powyższa metoda nie jest wymagana przez autoryzację HTTP typu "Basic", więc nie można na niej polegać. Testy z przeglądarką Lynx pokazały, że Lynx nie usuwa danych o autoryzacji po odebraniu od serwera kodu 401, zatem przejście wstecz a następnie do przodu otworzy stronę, chyba, że wymagania co do danych autoryzacji zmieniły się. Użytkownik może jednak użyć klawisza '_' by usunąc dane o autoryzacji.

Zwóć uwagę, że do wersji PHP 4.3.3, Autoryzacja HTTP nie działała na serwerze Microsoft IIS z PHP w wersji CGI z powodu ograniczeń IIS. Aby zmusić go do działania w PHP 4.3.3+ musisz wyedytować "Bezpieczeństwo katalogów" w konfiguracji IIS. Kliknij na "Edytuj" i zaznacz wyłącznie "Anonimowy Dostęp", wszystkie inne pola powinny pozostać nie zaznaczone.

Inne ogranicznie jest jeśli używasz modułu IIS (ISAPI) i PHP 4, nie możesz wtedy użyć zmiennych PHP_AUTH_* ale zamiast nich, dostępna jest zmienna HTTP_AUTHORIZATION. Na przykład rozważ następujący kod: list($user, $pw) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

Notatka IIS:: Aby Autoryzacja HTTP działała z IIS, dyrektywa PHP cgi.rfc2616_headers musi być ustawiona na 0 (domyślna wartość).

Notatka: Jeśli włączony jest tryb bezpieczny, uid skryptu jest doklejany do pola realm nagłówka WWW-Authenticate.