通过 PHP 注销 HTTP 身份验证

从 HTTP 身份验证保护文件夹注销的 正确方式是什么?

有一些变通方法可以实现这一点,但它们可能是危险的,因为它们可能存在 bug,或者在某些情况下/浏览器中无法工作。这就是为什么我正在寻找正确和清洁的解决方案。

119859 次浏览

通常,一旦浏览器要求用户提供凭据并将其提供给特定的网站,它将继续这样做,而不会进一步提示。与在客户端清除 cookie 的各种方法不同,我不知道有什么类似的方法可以让浏览器忘记提供的身份验证凭据。

AFAIK,在使用 htaccess (即基于 HTTP 的)身份验证时,没有干净的方法来实现“注销”功能。

这是因为这种身份验证使用 HTTP 错误代码“401”告诉浏览器需要凭据,此时浏览器提示用户输入详细信息。从那时起,直到浏览器关闭,它将始终发送凭据,而不会进一步提示。

解决方案 (不是一个干净、漂亮(甚至可以工作! 参见注释)的解决方案) :

一次破坏了他的证件。

您可以通过发送适当的头(如果没有登录)将 HTTP 身份验证逻辑移动到 PHP:

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

并使用以下命令解析输入:

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

因此,一次性禁用他的证书应该是微不足道的。

Mu. 没有正确的方法存在,甚至没有一个浏览器是一致的。

这个问题来自 HTTP 规范(第15.6节) :

现有的 HTTP 客户端和用户代理通常保留身份验证 HTTP/1.1. 没有为 服务器指示客户端丢弃这些缓存凭据。

另一方面,10.4.2章节说:

如果请求已包含授权凭据,则401 响应表明,对于那些 如果401响应包含与 事先响应,并且用户代理已经尝试 认证至少一次,然后用户应该显示 回应中给出的实体,因为该实体可能 包括有关的诊断资料。

换句话说,您可以再次显示登录框(如 @ Karsten所说) ,但浏览器不一定要满足你的要求-所以不要太依赖这个(错误的)特征。

方法在 Safari 中工作得很好,在 Firefox 和 Opera 中也可以工作,但是有一个警告。

Location: http://logout@yourserver.example.com/

这告诉浏览器用新的用户名打开 URL,覆盖前一个用户名。

到目前为止,我找到的最好的解决方案是(这是一种伪代码,$isLoggedIn是 http auth 的伪变量) :

在“注销”时,只需存储一些信息到会话中,说明用户实际上已经注销。

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

在检查身份验证的地方,我展开条件:

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

Session 在某种程度上与 http 身份验证的状态相关联,因此只要用户保持浏览器打开,并且只要 http 身份验证在浏览器中持续存在,用户就会保持注销状态。

我对这个问题的解决办法如下。您可以在本页的第二个示例 http://php.net/manual/en/features.http-auth.php中找到函数 http_digest_parse$realm$users

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;
}

简单的答案是您不能可靠地注销 http-身份验证。

长话短说:
HTTP-auth (与 HTTP 规范的其余部分一样)意味着是无状态的。因此,“登录”或“退出”并不是一个真正有意义的概念。查看它的更好方法是,对于每个 HTTP 请求(请记住,页面加载通常是多个请求) ,询问“是否允许执行请求?”.服务器将每个请求视为新的,与以前的任何请求无关。

浏览器选择记住您在第一个401上告诉它们的凭据,并在没有用户对后续请求的显式权限的情况下重新发送这些凭据。这是给用户提供他们所期望的“登录/退出”模型的一种尝试,但它纯粹是一个组合。是 浏览器模拟了这种状态的持久性。网络服务器完全没有意识到这一点。

因此,在 http-auth 的上下文中,“注销”纯粹是浏览器提供的模拟,因此超出了服务器的权限。

是的,有一些组件,但是它们破坏了 REST-ness (如果这对你有价值的话) ,而且它们是不可靠的。

如果您确实需要一个登录/登出模型来进行站点身份验证,那么最好的办法是使用跟踪 cookie,以某种方式(mysql、 sqlite、 latfile 等)在服务器上保存状态的持久性。这将需要对所有请求进行评估,例如,使用 PHP。

虽然其他人正确地说,它不可能从基本的 http 身份验证注销有方法来实现类似的 规矩点身份验证。一个显而易见的方法是使用 Auth _ memcookie。如果你真的想实现基本的 HTTP 身份验证(即使用浏览器对话框而不是 HTTP 表单来登录) ,只需将身份验证设置为一个独立的。Htaccess 保护目录,包含一个 PHP 脚本,该脚本在创建 memcache 会话后重定向到 te 用户所在的位置。

Trac-默认情况下-也使用 HTTP 身份验证。注销不起作用,无法修复:

  • 这是 HTTP 身份验证方案本身的一个问题,在 Trac 中我们无法正确地修复它。
  • 目前还没有适用于所有主流浏览器的解决方案(JavaScript 或其他)。

来自: 《 http://trac.edgewall.org/ticket/791#comment:103》

看起来这个问题没有可行的答案,这个问题已经在七年前被报道过了,而且它非常有意义: HTTP 是无状态的。请求是否使用身份验证凭据完成。但这是客户端发送请求的问题,而不是服务器接收请求的问题。服务器只能说明请求 URI 是否需要授权。

我需要重置.htaccess 授权,所以我使用了以下命令:

<?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;
}
?>

在这里找到的: Http://php.net/manual/en/features.http-auth.php

想想看。

许多解决方案驻留在这个页面上,它甚至在底部注明: Lynx,不像其他浏览器那样清除认证;)

我在已安装的浏览器上测试过,一旦关闭,每个浏览器似乎都需要重新进入 reauth。

分两步从 HTTP 基本授权注销

假设我有一个名为“ Password protected”的 HTTP 基本认证域,并且 Bob 已经登录。为了退出,我发出两个 AJAX 请求:

  1. 访问 script/logout _ step 1。它向. htusers 添加一个随机的临时用户,并用其登录名和密码进行响应。
  2. Access script/logout _ step 2 使用临时用户的登录名和密码进行身份验证. 该脚本删除临时用户并在响应中添加这个头: WWW-Authenticate: Basic realm="Password protected"

这时浏览器忘记了 Bob 的凭证。

也许我没抓住重点。

我发现结束 HTTP 身份验证最可靠的方法是关闭浏览器和所有浏览器窗口。你可以用 Javascript 关闭一个浏览器窗口,但我不认为你可以关闭所有的浏览器窗口。

解决办法

你可以使用 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>

以上所做的是:

  • 对于 IE -只需清除授权缓存并重定向某处

  • 对于其他浏览器 -在后台发送带有‘ logout’登录名和密码的 XMLHttpRequest。我们需要将它发送到某个路径,该路径将向该请求返回200 OK (即,它不应该要求 HTTP 身份验证)。

在注销之后用某个重定向到的路径替换 '/where/to/redirect',并用站点上返回200 OK 的某个路径替换 '/path/that/will/return/200/OK'

这可能不是我们想要的解决方案,但我是这样解决的。 我有2个注销过程的脚本。

注销

<?php
header("Location: http://.@domain.com/log.php");
?>

Log.php

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

这样我就不会收到警告,我的疗程也就结束了

我发现消除 PHP_AUTH_DIGESTPHP_AUTH_USERPHP_AUTH_PW凭据的唯一有效方法是调用头 HTTP/1.1 401 Unauthorized

function clear_admin_access(){
header('HTTP/1.1 401 Unauthorized');
die('Admin access turned off');
}

这里有很多非常复杂的答案。在我的特殊情况下,我发现了一个干净和简单的修复注销。我还没在 Edge 测试过。 在我登录的页面上,我放置了一个类似的注销链接:

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

并且在 logout.html 页面的头部(该页面也受。Htaccess)我有一个类似的页面刷新:

<meta http-equiv="Refresh" content="0; url=https://logout:logout@MyDomainHere.net/" />

在这里,您可以保留“注销”字样,以清除网站缓存的用户名和密码。

我承认,如果需要从一开始就能够直接登录到多个页面,那么每个入口点都需要自己对应的 logout.html 页面。否则,您可以通过在实际的登录提示符之前引入一个额外的看门人步骤来集中注销,这需要输入一个短语来到达登录的目的地。

我已经在一篇文章(https://www.hattonwebsolutions.co.uk/articles/how_to_logout_of_http_sessions)中总结了我的解决方案,但是我使用了一个 ajax 调用和2x htaccess 文件(正如这个问题中所建议的: 如何注销在谷歌浏览器中使用的 HTTP 认证(htaccess) ?)。

简而言之,你:

  1. 创建一个子文件夹,其中包含相同 AuthName 上的 htaccess 文件,但需要不同的用户
  2. 向页面发送一个 ajax 请求(使用错误的用户名)(失败) ,然后触发一个超时重定向到登出页面。

这样可以避免在注销文件夹中出现第二个弹出窗口,请求另一个用户名(这会使用户感到困惑)。我的文章使用 Jquery,但是应该可以避免这种情况。