通过PHP进行HTTP身份验证注销


151

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

有一些解决方法可以实现此目的,但它们可能会带来危险,因为它们可能有故障或在某些情况/浏览器中无法使用。这就是为什么我要寻找正确和清洁的解决方案。


请指定登出目的。这应该是强制注销(用户停用)吗?为用户提供简单的注销功能?还要别的吗?
卡斯滕

6
我不明白为什么这很重要,但这两种情况都是:根据应用程序内部条件以及典型的注销按钮停用。请解释为什么它很重要,我将直接在问题中进行编辑。
2009年

2
“正确和干净的解决方案”是浏览器具有自己的注销按钮,单击该按钮将使浏览器停止发送Auth标头...一个人可以做梦,对吗?
DanMan 2012年

1
Web Developer工具栏上有这样的“按钮”。
2012年

约瑟夫所说的话:Firefox的Web开发人员工具栏 ->Miscellaneous -> Clear Private Data -> HTTP Authentication
Yarin 2012年

Answers:


103

亩。没有正确的方法存在,甚至没有跨浏览器一致的方法。

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

现有的HTTP客户端和用户代理通常会无限期地保留身份验证信息。HTTP / 1.1。没有为服务器提供一种方法来指示客户端丢弃这些缓存的凭据。

另一方面,第10.4.2节说:

如果请求已包含授权凭据,则401响应指示已拒绝这些凭据的授权。如果401响应包含与先前响应相同的质询,并且用户代理已经尝试了至少一次身份验证,则应该向用户提供响应中给定的实体,因为该实体可能包括相关的诊断信息。

换句话说,您也许可以再次显示登录框(如@Karsten所说),但是浏览器不必满足您的请求 -因此,不要过多地依赖此功能。


9
这是RFC中的错误。W3C懒得修复。好难过。
Erik Aronesty

正如@Jonathan Hanson 在下面建议的那样,您可以将跟踪cookie与HTTP身份验证一起使用。这对我来说是最好的方法。
machineaddict

61

在Safari中效果很好的方法。在Firefox和Opera中也可以使用,但带有警告。

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

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


14
根据RFC 3986(URI:通用语法)第3.2.1节。(用户信息)的使用user:password@host已弃用。仅使用http://logout@yourserver.example.com/不是,并且在大多数情况下应该可以使用。
AEF

1
@andho:是的,这是重定向。您应该以状态302使用它
。– Kornel

1
显然,也可以使用一个简单的logout@yourserver.example.com链接(此URL的“断开连接”链接)代替PHP中的HTTP重定向……这有何缺点?
moala 2012年

4
当心:重新登录(使用注销提示登录)后,使用相对路径提交表单可能会失败,因为该地址仍然是logout@yourserver.example.com/path而不是yourserver.example.com/path /
杰森(Jason)

1
logout@yourserver.example.com在Chrome中工作不正常,但在Firefox中提示安全问题。注销:true@yourserver.example.com不会使Firefox promt成为安全问题。这两个网址在IE8中都不起作用:/
Thor A. Pedersen 2012年

46

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

长答案:
Http-auth(与HTTP规范的其余部分一样)是无状态的。因此,“登录”或“注销”并不是一个有意义的概念。看到它的更好的方法是针对每个HTTP请求(记住页面加载通常是多个请求)询问“是否允许您执行请求的操作?”。服务器将每个请求视为新请求,并且与以前的任何请求都不相关。

浏览器已选择记住您在第一个401上告诉他们的凭据,然后在没有用户对后续请求的明确许可的情况下重新发送它们。这是为用户提供他们期望的“已登录/已注销”模型的尝试,但这纯粹是一种错误。正是浏览器在模拟这种状态的持久性。Web服务器完全不知道它。

因此,在http-auth上下文中,“注销”纯粹是由浏览器提供的模拟,因此不在服务器权限范围内。

是的,有仇。但是它们破坏了RESTful-ness(如果这对您来说很有价值),并且它们不可靠。

如果您绝对需要登录/注销模型来进行站点认证,那么最好的选择是跟踪cookie,并以某种方式(mysql,sqlite,flatfile等)将状态的持久性存储在服务器上。这将要求评估所有请求,例如使用PHP。


26

解决方法

您可以使用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-只需清除身份验证缓存并重定向到某处

  • 对于其他浏览器 -在后台使用“注销”登录名和密码发送XMLHttpRequest。我们需要将其发送到某个路径,该路径将对该请求返回200 OK(即,它不需要HTTP身份验证)。

更换'/where/to/redirect'一些路径注销后重定向到并替换'/path/that/will/return/200/OK'与您网站上的一些路径,将返回200 OK。


5
以其他用户身份登录是一种解决方法。但这实际上是有效的,值得更多的赞誉。
CharlieRudenstål2013年

2
我认为这是最好的答案。正如指出这个回答类似的问题可能会有一些优势,随机密码。
zelanix 2014年

2
这就是我想要的-在所有浏览器中都可以正常工作。保留了我继承的“注销”页面。我不一定要使用JS(可能不合理),但是其他答案都存在跨浏览器的问题,这很完美。
dgig 2014年

我无法按照说明的方式进行这项工作。当我返回到安全区域时,浏览器通过发送标头中最后使用的有效凭据来再次对其进行身份验证。但是,稍作更改就可以为我工作。我用与安全区域相同的领域的标头更改了200 OK响应,但是仅接受“ logout:logout”用户/密码。这样,该用户使用该“注销”用户登录,并且该用户在返回安全区域时将重试。安全区域拒绝此用户/密码,因此用户可以更改其凭据。
jonaguera 2015年

2
正如解释的那样,这不起作用。经测试在Chrome 40和Firefox 35
funforums

13

解决方法(不是一个干净的,不错的(甚至无法工作!请参见评论)解决方案):

一次禁用他的凭据。

您可以通过发送适当的标头(如果未登录)将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

因此,一次禁用他的凭据应该是微不足道的。


18
此解决方案的问题是:您让IE知道凭据不正确。它显示带有空字段的登录对话框(不显示密码管理器中存储的值)。但是,当您单击“取消”并刷新页面时,它会发送存储的凭据,从而再次登录。
2009年

不赞成投票;就像约瑟夫·萨布尔(Josef Sable)所说,这并不能解决当前的问题。
克里斯·韦瑟林

7

分两步从HTTP Basic Auth注销

假设我有一个名为“密码保护”的HTTP基本身份验证领域,Bob已登录。要注销,我发出2个AJAX请求:

  1. 访问脚本/ logout_step1。它将随机的临时用户添加到.htusers,并使用其登录名和密码进行响应。
  2. 使用临时用户的登录名和密码对访问脚本/ logout_step2进行身份验证。该脚本删除临时用户,并在响应中添加此标头:WWW-Authenticate: Basic realm="Password protected"

此时,浏览器忘记了Bob的凭据。


1
哇!即使绝对是一件很愚蠢的事情,这绝对值得+1。
安迪·特里格斯

7

我对这个问题的解决方法如下。您可以找到该函数http_digest_parse$realm并且$users在此页面的第二个示例中:http : //php.net/manual/zh/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;
}

4

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


我相信当您在Firefox中选择“删除私人数据”时,有一个选项可以删除经过身份验证的会话
Kristian J. 2009年

1
Firefox的Web开发人员工具栏扩展程序还提供删除HTTP身份验证的功能。但这是没有问题的,因为我们真的不能要求我们的用户下载FF扩展名或运行神秘的浏览器命令:-)
JosefSábl09年

2
Firefox的默认HTTP身份验证注销方法可在“工具”>“清除最近的历史记录...”下找到,作为复选框“活动登录名”。这既不直观,也不允许您仅注销一个域,而您始终注销每一页。
AEF

2

Trac(默认情况下)也使用HTTP身份验证。注销不起作用,无法解决:

  • 这是HTTP身份验证方案本身的问题,在Trac中我们无法采取任何措施来正确修复它。
  • 当前尚没有适用于所有主要浏览器的解决方法(JavaScript或其他)。

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

看起来该问题没有有效的答案,该问题已在7年前报告过,这很有意义:HTTP是无状态的。是否使用身份验证凭据来完成请求。但这是客户端发送请求的问题,而不是服务器接收请求的问题。服务器只能说出请求URI是否需要授权。


2

我需要重置.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,不会像其他浏览器一样清除auth;)

我在已安装的浏览器上对其进行了测试,并且关闭后,每个浏览器似乎始终需要在重新输入时进行重新验证。


这似乎不起作用,我得到的取消文本没有任何弹出式登录框。
迈克尔

原来,发送WWW-Authenticate导致问题的信息,自动删除导致我退出的信息。
迈克尔

相反,似乎在一个浏览器(Chrome)中在解决问题时发送WWW-Authenticate通知会导致另一浏览器(Firefox)记住凭据,并在下一个请求时发送凭据,从而导致自动重新登录!啊!
迈克尔

然后查看UA,然后做一个或另一个似乎是解决方案
Lennart Rolland 2015年

2

这可能不是寻找的解决方案,但我这样解决了。我有2个脚本用于注销过程。

logout.php

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

log.php

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

这样,我不会收到警告,并且会话会终止


1
这是真正对我有用的唯一解决方案!在Firefox 37和Chromium 41上进行了测试
zesaver 2015年

1

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

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


1

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

在“注销”时,只需将一些信息存储到会话中即可表明该用户实际上已注销。

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

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

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

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


4
虽然http基本身份验证是RESTful的,但会话不是。
守护进程

1

也许我错过了重点。

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


如果某些浏览器是唯一打开的选项卡,则某些浏览器将不会关闭该窗口,因此,要点真的很重要
scape

那些年前,我有个任务是在不关闭窗口的情况下实现注销按钮:-)但是,也许他们不愿谈论“不关闭窗口”。但是,嘿,这是一个简单的解决方案,可能对某人有用,老实说,我当时想念它。
约瑟夫·萨布(JosefSábl)

1

我发现消灭 PHP_AUTH_DIGEST or PHP_AUTH_USERAND PHP_AUTH_PW凭据是调用标头HTTP/1.1 401 Unauthorized

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

0

当其他人在说,是正确的它不可能从基本的HTTP认证注销有实现其认证的方式表现相似。一种明显的方法是使用auth_memcookie。如果您确实想使用此方法来实现基本的HTTP身份验证(即使用浏览器对话框登录而不是HTTP形式),只需将身份验证设置为一个单独的.htaccess保护目录,其中包含一个PHP脚本,该脚本会将用户重定向到后面的位置创建内存缓存会话。


0

这里有很多很棒的-复杂的-答案。在我的特殊情况下,我找到了一个干净而简单的注销解决方法。我尚未在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页面。否则,您可以通过在实际登录提示之前在过程中引入一个额外的网守步骤来集中注销,这需要输入短语才能到达登录目标。


1
继续前进时,它可以注销,但是浏览器的后退历史记录仍可以重新建立会话。
johnwayne
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.