Wylogowanie z uwierzytelnianiem HTTP przez PHP

151

Jaki jest prawidłowy sposób wylogowania się z folderu chronionego uwierzytelnianiem HTTP?

Istnieją obejścia, które mogą to osiągnąć, ale są one potencjalnie niebezpieczne, ponieważ mogą zawierać błędy lub nie działać w niektórych sytuacjach / przeglądarkach. Dlatego szukam poprawnego i czystego rozwiązania.

Josef Sábl
źródło
Określ cel wylogowania. Czy powinno to być wymuszone wylogowanie (dezaktywacja użytkownika)? Prosta funkcja wylogowania dla użytkownika? Coś jeszcze?
Karsten,
6
Nie rozumiem, dlaczego to ma znaczenie, ale w obu przypadkach: dezaktywacja na podstawie warunków wewnętrznych w aplikacji oraz typowy przycisk wylogowania. Proszę wyjaśnić, dlaczego jest to ważne, poprawię to bezpośrednio w pytaniu.
Josef Sábl
2
"Prawidłowym i czystym rozwiązaniem" byłyby przeglądarki posiadające własny przycisk wylogowania, który po kliknięciu spowoduje, że przeglądarka przestanie wysyłać nagłówki Auth ... Można marzyć, prawda?
DanMan,
1
Web Developer Toolbar ma taki „przycisk”.
Josef Sábl
Co powiedział Josef: pasek narzędzi programisty dla przeglądarki Firefox ->Miscellaneous -> Clear Private Data -> HTTP Authentication
Yarin

Odpowiedzi:

103

Mu. Nie istnieje poprawny sposób , nawet taki, który byłby spójny w różnych przeglądarkach.

To jest problem, który wynika ze specyfikacji HTTP (sekcja 15.6):

Istniejący klienci HTTP i agenci użytkownika zazwyczaj zachowują informacje uwierzytelniające przez czas nieokreślony. HTTP / 1.1. nie zapewnia metody umożliwiającej serwerowi kierowanie klientów do odrzucenia tych buforowanych poświadczeń.

Z drugiej strony, sekcja 10.4.2 mówi:

Jeśli żądanie zawierało już poświadczenia autoryzacji, odpowiedź 401 wskazuje, że odmówiono autoryzacji dla tych poświadczeń. Jeżeli odpowiedź 401 zawiera to samo wezwanie, co poprzednia odpowiedź, a agent użytkownika już co najmniej raz próbował uwierzytelnić, to użytkownikowi POWINIEN zostać przedstawiona jednostka, która została podana w odpowiedzi, ponieważ jednostka ta może zawierać istotne informacje diagnostyczne.

Innymi słowy, możesz być w stanie ponownie wyświetlić okno logowania (jak mówi @Karsten ), ale przeglądarka nie musi honorować twojego żądania - więc nie polegaj zbytnio na tej (niewłaściwej) funkcji.

Piskvor opuścił budynek
źródło
9
To jest błąd w RFC. W3C zbyt leniwy, aby naprawić. Taki smutny.
Erik Aronesty,
Jak @Jonathan Hanson zasugerował poniżej , możesz używać śledzącego pliku cookie wraz z uwierzytelnianiem HTTP. To dla mnie najlepsza metoda.
machineaddict
61

Metoda, która działa dobrze w Safari. Działa również w Firefoksie i Operze, ale z ostrzeżeniem.

Location: http://[email protected]/

Mówi to przeglądarce, aby otworzyła adres URL z nową nazwą użytkownika, zastępując poprzednią.

Kornel
źródło
14
Zgodnie z RFC 3986 (URI: Generic Syntax) sekcja 3.2.1. (Informacje o użytkowniku) użycie user:password@hostjest przestarzałe. Używanie samego tylko http://[email protected]/nie jest i powinno działać w większości przypadków.
aef
1
@andho: tak, to przekierowanie. Powinieneś go używać ze statusem 302.
Kornel
1
Wygląda na to, że prosty link do [email protected] również działa (link "rozłącz" do tego adresu URL) zamiast przekierowania http w PHP ... Jakieś wady?
moala
4
Uwaga: składanie formularzy przy użyciu ścieżki względnej może zakończyć się niepowodzeniem, gdy jest dokonywane po ponownym logowania (login z wylogowaniem Podpowiedź), ponieważ adres będzie nadal [email protected]/path i nie yourserver.example.com/path /
Jason
1
[email protected] działa bez problemu w przeglądarce Chrome, ale wyświetla pytanie dotyczące bezpieczeństwa w przeglądarce Firefox. logout: [email protected] nie sprawi, że Firefox będzie pytać o bezpieczeństwo. Żaden z dwóch adresów URL nie działa w IE8: /
Thor A. Pedersen
46

Prosta odpowiedź jest taka, że ​​nie można niezawodnie wylogować się z uwierzytelniania http.

Długa odpowiedź:
Http-auth (podobnie jak reszta specyfikacji HTTP) ma być bezstanowe. Zatem bycie „zalogowanym” lub „wylogowanym” nie jest tak naprawdę koncepcją, która ma sens. Lepszym sposobem, aby to zobaczyć, jest pytanie, przy każdym żądaniu HTTP (pamiętaj, że ładowanie strony to zwykle wiele żądań) „czy możesz zrobić to, o co prosisz?”. Serwer postrzega każde żądanie jako nowe i niezwiązane z żadnymi wcześniejszymi żądaniami.

Przeglądarki zdecydowały się zapamiętywać dane uwierzytelniające, które podajesz im w pierwszym 401, i wysyłać je ponownie bez wyraźnej zgody użytkownika na kolejne żądania. Jest to próba pokazania użytkownikowi modelu "zalogowania / wylogowania", jakiego oczekują, ale jest to czysta kludge. To przeglądarka symuluje tę trwałość stanu. Serwer sieciowy jest tego całkowicie nieświadomy.

Tak więc „wylogowanie” w kontekście http-auth jest czystą symulacją zapewnianą przez przeglądarkę, a więc poza władzą serwera.

Tak, są kludges. Ale łamią RESTful-ness (jeśli jest to dla ciebie wartościowe) i są zawodne.

Jeśli absolutnie potrzebujesz modelu zalogowania / wylogowania do uwierzytelnienia swojej witryny, najlepszym rozwiązaniem jest śledzący plik cookie, z trwałością stanu przechowywanego w jakiś sposób na serwerze (mysql, sqlite, flatfile itp.). Będzie to wymagało oceny wszystkich żądań, na przykład za pomocą PHP.

Jonathan Hanson
źródło
26

Obejście problemu

Możesz to zrobić za pomocą Javascript:

<html><head>
<script type="text/javascript">
function logout() {
    var xmlhttp;
    if (window.XMLHttpRequest) {
          xmlhttp = new XMLHttpRequest();
    }
    // code for IE
    else if (window.ActiveXObject) {
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (window.ActiveXObject) {
      // IE clear HTTP Authentication
      document.execCommand("ClearAuthenticationCache");
      window.location.href='/where/to/redirect';
    } else {
        xmlhttp.open("GET", '/path/that/will/return/200/OK', true, "logout", "logout");
        xmlhttp.send("");
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4) {window.location.href='/where/to/redirect';}
        }


    }


    return false;
}
</script>
</head>
<body>
<a href="#" onclick="logout();">Log out</a>
</body>
</html>

To, co zostało zrobione powyżej, to:

  • dla IE - po prostu wyczyść pamięć podręczną uwierzytelniania i przekieruj gdzieś

  • w przypadku innych przeglądarek - wyślij zakulisowe żądanie XMLHttpRequest z nazwą logowania i hasłem „wylogowania”. Musimy wysłać go na jakąś ścieżkę, która zwróci 200 OK na to żądanie (tj. Nie powinno wymagać uwierzytelnienia HTTP).

Zastąp '/where/to/redirect'jakąś ścieżką do przekierowania po wylogowaniu się i zastąp '/path/that/will/return/200/OK'jakąś ścieżką w swojej witrynie, która zwróci 200 OK.

Anton Mochalin
źródło
5
Logowanie jako inny użytkownik jest pewnym obejściem. Ale to faktycznie działa i zasługuje na więcej uznania.
Charlie Rudenstål
2
Myślę, że to najlepsza odpowiedź. Jak stwierdzono w tej odpowiedzi na podobne pytanie, losowanie hasła może przynieść pewne korzyści.
zelanix
2
Tego właśnie chciałem - działało we wszystkich przeglądarkach bez żadnych problemów. Strona „wylogowania”, którą odziedziczyłem, pozostała nienaruszona. Niekoniecznie chciałem używać JS (może irracjonalnie), ale wszystkie inne odpowiedzi dotyczyły problemów z różnymi przeglądarkami i to działało idealnie.
dgig
Nie mogę sprawić, żeby to zadziałało w sposób wyjaśniony. Kiedy wróciłem do zabezpieczonego obszaru, przeglądarka ponownie uwierzytelnia się, wysyłając ostatnio używane prawidłowe dane uwierzytelniające w nagłówku. Jednak z niewielką zmianą zadziałało to dla mnie. Zmieniłem odpowiedź 200 OK na nagłówek z tym samym obszarem obszaru zabezpieczonego, ale akceptując tylko użytkownika / przepustkę "wyloguj: wyloguj". W ten sposób użytkownik zalogował się jako użytkownik „wyloguj się” i jest to użytkownik, który ponawia próbę, gdy wraca do obszaru chronionego. Obszar zabezpieczony odrzuca tego użytkownika / przepustkę, więc użytkownik może zmienić swoje poświadczenia.
jonaguera
2
To nie działa, jak zostało wyjaśnione. Testowane w Chrome 40 i Firefox 35.
funforums
13

Obejście problemu (nie jest to czyste, ładne (lub nawet działające! Zobacz komentarze) rozwiązanie):

Jednokrotnie wyłącz jego poświadczenia.

Możesz przenieść logikę uwierzytelniania HTTP do PHP, wysyłając odpowiednie nagłówki (jeśli nie jesteś zalogowany):

Header('WWW-Authenticate: Basic realm="protected area"');
Header('HTTP/1.0 401 Unauthorized');

I parsowanie danych wejściowych za pomocą:

$_SERVER['PHP_AUTH_USER'] // httpauth-user
$_SERVER['PHP_AUTH_PW']   // httpauth-password

Dlatego jednorazowe wyłączenie jego danych uwierzytelniających powinno być trywialne.

Karsten
źródło
18
Problem z tym rozwiązaniem polega na tym, że: Poinformujesz IE, że poświadczenia nie są prawidłowe. Wyświetla okno logowania z pustymi polami (bez wartości przechowywanych w menedżerze haseł). Ale kiedy klikniesz anuluj i odświeżysz stronę, wysyła zapisane poświadczenia, logując się ponownie.
Josef Sábl,
Głosowano w dół; Jak skomentował Josef Sable, nie rozwiązuje to problemu.
Chris Wesseling
7

Wyloguj się z HTTP Basic Auth w dwóch krokach

Załóżmy, że mam dziedzinę uwierzytelniania podstawowego HTTP o nazwie „Ochrona hasłem”, a Robert jest zalogowany. Aby się wylogować, wykonuję 2 żądania AJAX:

  1. Uzyskaj dostęp do skryptu / logout_step1. Dodaje losowego użytkownika tymczasowego do .htusers i odpowiada jego loginem i hasłem.
  2. Skrypt dostępu / logout_step2 uwierzytelniony za pomocą loginu i hasła użytkownika tymczasowego . Skrypt usuwa tymczasowego użytkownika i dodaje ten nagłówek do odpowiedzi:WWW-Authenticate: Basic realm="Password protected"

W tym momencie przeglądarka zapomniała danych logowania Boba.

Vlad GURDIGA
źródło
1
Łał! To naprawdę zasługuje na +1 za czystą pomysłowość, nawet jeśli jest to całkowicie szalone.
Andy Triggs
7

Moje rozwiązanie problemu jest następujące. Możesz znaleźć tę funkcję http_digest_parse, $realmaw $usersdrugim przykładzie tej strony: http://php.net/manual/en/features.http-auth.php .

session_start();

function LogOut() {
  session_destroy();
  session_unset($_SESSION['session_id']);
  session_unset($_SESSION['logged']);

  header("Location: /", TRUE, 301);   
}

function Login(){

  global $realm;

  if (empty($_SESSION['session_id'])) {
    session_regenerate_id();
    $_SESSION['session_id'] = session_id();
  }

  if (!IsAuthenticated()) {  
    header('HTTP/1.1 401 Unauthorized');
    header('WWW-Authenticate: Digest realm="'.$realm.
   '",qop="auth",nonce="'.$_SESSION['session_id'].'",opaque="'.md5($realm).'"');
    $_SESSION['logged'] = False;
    die('Access denied.');
  }
  $_SESSION['logged'] = True;  
}

function IsAuthenticated(){
  global $realm;
  global $users;


  if  (empty($_SERVER['PHP_AUTH_DIGEST']))
      return False;

  // check PHP_AUTH_DIGEST
  if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
     !isset($users[$data['username']]))
     return False;// invalid username


  $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
  $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);

  // Give session id instead of data['nonce']
  $valid_response =   md5($A1.':'.$_SESSION['session_id'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

  if ($data['response'] != $valid_response)
    return False;

  return True;
}
Pie86
źródło
4

Zwykle, gdy przeglądarka poprosi użytkownika o dane uwierzytelniające i poda je do określonej witryny internetowej, będzie to robić bez dalszych monitów. W przeciwieństwie do różnych sposobów usuwania plików cookie po stronie klienta, nie znam podobnego sposobu, aby poprosić przeglądarkę o zapomnienie dostarczonych danych uwierzytelniających.

Greg Hewgill
źródło
Wydaje mi się, że istnieje możliwość usunięcia uwierzytelnionych sesji po wybraniu opcji „Usuń prywatne dane” w przeglądarce Firefox
Kristian J.
1
Również rozszerzenie Web Developer Toolbar dla przeglądarki Firefox oferuje funkcję usuwania uwierzytelnień HTTP. Ale to nie wchodzi w rachubę, ponieważ naprawdę nie możemy prosić naszych użytkowników o pobieranie rozszerzeń FF lub uruchamianie tajemniczych poleceń przeglądarki :-)
Josef Sábl
2
Domyślny sposób wylogowania się z uwierzytelniania HTTP przez przeglądarkę Firefox jest dostępny w sekcji „Narzędzia”> „Wyczyść historię ostatnich ...”, jako pole wyboru „Aktywne logowania”. Nie jest to ani intuicyjne, ani nie pozwala na wylogowanie się tylko z jednej domeny, zawsze wylogowujesz się z każdej strony.
aef
2

Trac - domyślnie - również używa uwierzytelniania HTTP. Wylogowanie nie działa i nie można go naprawić:

  • Jest to problem z samym schematem uwierzytelniania HTTP i nie możemy nic zrobić w Tracu, aby to naprawić.
  • Obecnie nie ma obejścia (JavaScript lub innego), które działałoby we wszystkich głównych przeglądarkach.

Od: http://trac.edgewall.org/ticket/791#comment:103

Wygląda na to, że nie ma działającej odpowiedzi na to pytanie, problem został zgłoszony siedem lat temu i ma to sens: HTTP jest bezstanowe. Żądanie jest realizowane z poświadczeniami uwierzytelniającymi lub nie. Ale to kwestia tego, że klient wysyła żądanie, a nie serwer, który je otrzymuje. Serwer może tylko powiedzieć, czy identyfikator URI żądania wymaga autoryzacji, czy nie.

hakre
źródło
2

Musiałem zresetować autoryzację .htaccess, więc użyłem tego:

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Text to send if user hits Cancel button';
    exit;
}
?>

Znalazłem go tutaj: http://php.net/manual/en/features.http-auth.php

Domyśl.

Wiele rozwiązań znajduje się na tej stronie, a nawet zauważa na dole: Lynx, nie czyści autoryzacji tak jak inne przeglądarki;)

Przetestowałem to na moich zainstalowanych przeglądarkach i po zamknięciu wydaje się, że każda przeglądarka konsekwentnie wymaga ponownego uwierzytelnienia przy ponownym wejściu.

Dooley
źródło
Wygląda na to, że to nie działa, otrzymuję tekst anulowania bez wyskakującego okienka logowania.
Michael
Okazało się, że wysłanie pliku WWW-Authenticatebyło przyczyną problemu, pozbycie się tego automatycznie wylogowało mnie.
Michael
I odwrotnie, wydaje się, że NIE wysłanie WWW-Authenticatepodczas naprawiania problemu w jednej przeglądarce (Chrome) powoduje, że inna przeglądarka (Firefox) zapamiętuje dane uwierzytelniające i wyśle ​​je przy następnym żądaniu, co skutkuje automatycznym ponownym zalogowaniem! Argh!
Michael
Następnie spójrz na UA i zrób jedno lub drugie, które wydaje się rozwiązaniem
Lennart Rolland
2

Może to nie jest rozwiązanie, którego szukałem, ale rozwiązałem to w ten sposób. Mam 2 skrypty do procesu wylogowania.

logout.php

<?php
header("Location: http://[email protected]/log.php");
?>

log.php

<?php
header("location: https://google.com");
?>

W ten sposób nie dostaję ostrzeżenia i moja sesja zostanie zakończona

Kevin
źródło
1
To było jedyne rozwiązanie, które naprawdę zadziałało! Przetestowano w przeglądarce Firefox 37 i Chromium 41
zesaver
1

AFAIK, nie ma prostego sposobu na zaimplementowanie funkcji „wylogowania” podczas korzystania z uwierzytelniania htaccess (tj. Opartego na protokole HTTP).

Dzieje się tak, ponieważ takie uwierzytelnianie wykorzystuje kod błędu HTTP „401”, aby poinformować przeglądarkę, że wymagane są poświadczenia, po czym przeglądarka pyta użytkownika o szczegóły. Od tego momentu, dopóki przeglądarka nie zostanie zamknięta, zawsze będzie wysyłać dane uwierzytelniające bez dalszych monitów.

Alnitak
źródło
1

Najlepszym rozwiązaniem, jakie do tej pory znalazłem, jest (jest to rodzaj pseudokodu, czyli $isLoggedIn pseudo zmienna is dla uwierzytelniania http):

W momencie "wylogowania" po prostu zapisz do sesji informację, że użytkownik jest faktycznie wylogowany.

function logout()
{
  //$isLoggedIn = false; //This does not work (point of this question)
  $_SESSION['logout'] = true;
}

W miejscu, w którym sprawdzam uwierzytelnienie rozszerzam warunek:

function isLoggedIn()
{
  return $isLoggedIn && !$_SESSION['logout'];
}

Sesja jest w pewnym stopniu powiązana ze stanem uwierzytelniania http, więc użytkownik pozostaje wylogowany, o ile ma otwartą przeglądarkę i tak długo, jak uwierzytelnianie HTTP utrzymuje się w przeglądarce.

Josef Sábl
źródło
4
Podczas gdy podstawowe uwierzytelnianie HTTP jest zgodne z REST, sesje nie.
deamon
1

Może nie rozumiem.

Najbardziej niezawodny sposób zakończenia uwierzytelniania HTTP to zamknięcie przeglądarki i wszystkich okien przeglądarki. Możesz zamknąć okno przeglądarki za pomocą Javascript, ale nie sądzę, że możesz zamknąć wszystkie okna przeglądarki.

Toby Allen
źródło
fyi niektóre przeglądarki nie zamkną okna, jeśli jest to jedyna otwarta karta, więc kwestia jest naprawdę dyskusyjna
scape
Dawno temu miałem za zadanie zaimplementować przycisk wylogowania bez zamykania okna :-) Ale może nie rozwodziliby się nad tym, żeby „nie zamykać okna”. Ale hej, to jest proste rozwiązanie, które może zadziałać dla kogoś i szczerze mówiąc, brakowało mi go wtedy.
Josef Sábl
1

Jedynym skutecznym sposobem, w jaki udało mi się usunąć dane uwierzytelniające PHP_AUTH_DIGESTlub PHP_AUTH_USERAND PHP_AUTH_PW, jest wywołanie nagłówka HTTP/1.1 401 Unauthorized.

function clear_admin_access(){
    header('HTTP/1.1 401 Unauthorized');
    die('Admin access turned off');
}
OKRĄG
źródło
0

Podczas gdy inni mają rację mówiąc, że niemożliwe jest wylogowanie się z podstawowego uwierzytelniania HTTP, istnieją sposoby implementacji uwierzytelniania, które zachowują się podobnie. Jednym z oczywistych appeoach jest użycie auth_memcookie . Jeśli naprawdę chcesz zaimplementować podstawowe uwierzytelnianie HTTP (tj. Użyj okien dialogowych przeglądarki do logowania zamiast formularza HTTP) używając tego - po prostu ustaw uwierzytelnianie na oddzielny chroniony katalog .htaccess zawierający skrypt PHP, który przekierowuje z powrotem tam, gdzie przyszedł użytkownik tworzenie sesji memcache.

symcbean
źródło
0

Jest tu wiele świetnych - złożonych - odpowiedzi. W moim konkretnym przypadku znalazłem czystą i prostą poprawkę do wylogowania. Jeszcze nie testowałem w Edge. Na mojej stronie, na której się zalogowałem, umieściłem link do wylogowania podobny do tego:

<a href="https://MyDomainHere.net/logout.html">logout</a>

A w nagłówku tej strony logout.html (która jest również chroniona przez .htaccess) mam odświeżenie strony podobne do tego:

<meta http-equiv="Refresh" content="0; url=https://logout:[email protected]/" />

Miejsce, w którym pozostawisz słowa „wyloguj się”, aby wyczyścić nazwę użytkownika i hasło zapisane w pamięci podręcznej witryny.

Przyznam, że gdyby wiele stron musiało być bezpośrednio zalogowanych od samego początku, każdy z tych punktów wejścia musiałby mieć własną stronę logout.html. W przeciwnym razie możesz scentralizować wylogowanie, wprowadzając dodatkowy krok strażnika do procesu przed właściwym monitem logowania, wymagający wprowadzenia frazy, aby dotrzeć do miejsca docelowego logowania.

John wayne
źródło
1
przy przejściu do przodu to działa, wylogowuje się, ale historia wsteczna przeglądarki nadal może ponownie ustanowić sesję.
johnwayne