通过PHP实现HTTP身份验证注销

158

如何正确地退出HTTP身份验证保护文件夹?

虽然有一些方法可以实现这一点,但它们潜在的危险性很高,因为它们可能存在漏洞或在某些情况/浏览器中无法工作。这就是为什么我正在寻找正确而干净的解决方案。


请说明您注销的目的。这应该是强制注销(用户停用)吗?还是仅为用户提供简单的注销功能?还有其他需要考虑的吗? - Karsten
6
我不明白为什么这很重要,但它包括两种情况:基于应用内部条件的停用和常规的注销按钮。请解释一下为什么它很重要,我会直接将答案编辑到问题中。 - Josef Sábl
2
“正确而干净的解决方案”是浏览器拥有自己的注销按钮,当单击该按钮时,浏览器将停止发送Auth标头...一个人可以做梦,对吧? - DanMan
1
Web开发者工具栏有这样的“按钮”。 - Josef Sábl
Josef说的是:Firefox网页开发者工具栏 -> 杂项 -> 清除私人数据 -> HTTP身份验证 - Yarin
19个回答

105

Mu. 没有正确的方法存在,甚至没有一种方法可以在所有浏览器上保持一致。

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

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

另一方面,第10.4.2节说:

如果请求已经包含授权凭据,则401响应表示对这些凭据已拒绝授权。如果401响应包含与先前响应相同的挑战,并且用户代理已经尝试过至少一次身份验证,则应向用户呈现在响应中给出的实体,因为该实体可能包含相关诊断信息。

换句话说,您可能能够再次显示登录框(就像@Karsten所说的那样),但浏览器不一定会遵守您的请求 - 因此不要过度依赖这种(误导性的)功能。


9
这是RFC上的一个漏洞,W3C太懒了,不想修复。真遗憾。 - Erik Aronesty
正如@Jonathan Hanson在下面提到的那样,您可以使用跟踪cookie和HTTP身份验证。这对我来说是最好的方法。 - machineaddict

62

这种方法在Safari中效果良好,在Firefox和Opera中也可以使用,但会有警告。

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

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


14
根据RFC3986(URI通用语法)3.2.1节(用户信息),使用user:password@host已被弃用。只使用http://logout@yourserver.example.com/在大多数情况下可以正常工作。 - aef
1
@andho:是的,这是一个重定向。你应该使用状态码302。 - Kornel
1
显然,链接到http://logout@yourserver.example.com/也可以起作用(指向此URL的“断开连接”链接),而不是在PHP中进行http重定向...这样做有什么不利之处吗? - moala
4
注意:使用相对路径进行表单提交可能会在重新登录(使用注销提示进行登录)后失败,因为地址仍将是logout@yourserver.example.com/path而不是yourserver.example.com/path/ - Jason
1
http://logout@yourserver.example.com 在Chrome中没有问题,但在Firefox中会提示安全问题。http://logout:true@yourserver.example.com 不会让Firefox提示安全问题。这两个URL在IE8中都无法使用 :/ - Thor A. Pedersen
显示剩余3条评论

47
简单来说,你无法可靠地退出http身份验证。
长话短说:
Http-auth(像HTTP规范的其余部分一样)旨在是无状态的。因此,“登录”或“注销”并不是一个真正有意义的概念。更好的方式是问每个HTTP请求(记住一个页面加载通常是多次请求),“您是否被允许执行您正在请求的操作?”。服务器将每个请求视为新的且与任何先前的请求无关。
浏览器选择记住您在第一次401时告诉它们的凭据,并在后续请求中未经用户明确许可而重新发送它们。这是一种试图给用户期望的“登录/注销”模型的尝试,但这纯粹是一个修补程序。这是由浏览器模拟的状态持久性。Web服务器完全不知道它。
因此,在http-auth上下文中,“注销”纯粹是浏览器提供的模拟,因此超出了服务器的权限。
是的,有修补程序。但它们会破坏RESTful-ness(如果这对您有价值),而且它们不可靠。
如果您绝对需要站点身份验证的登录/注销模型,则最好的选择是跟踪cookie,并以某种方式在服务器上存储状态的持久性(mysql,sqlite,flatfile等)。这将要求所有请求进行评估,例如使用PHP。

27

解决方案

您可以使用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
用另一个用户登录有点绕,但这确实可行,值得更多肯定。 - Charlie Rudenstål
2
我认为这是最好的答案。正如在这个类似问题的回答中所述,随机化密码可能有一些优势。 - zelanix
2
这正是我想要的 - 在所有浏览器中都能正常工作,没有任何问题。保持了我继承的“注销”页面完整。我并不一定想使用JS(也许有些不理性),但其他答案都存在跨浏览器问题,而这个方法完美地解决了这个问题。 - dmgig
2
这个并不像它所解释的那样工作。在Chrome 40和Firefox 35中进行了测试。 - funforums
在Safari 9中运行良好。 - Matthieu
显示剩余2条评论

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知道凭据不正确。它显示登录对话框,并清空字段(不显示存储在密码管理器中的值)。但是,当你点击取消并刷新页面时,它会发送存储的凭据,从而再次登录。 - Josef Sábl
被踩了;像Josef Sable评论的那样,这并没有解决手头的问题。 - Chris Wesseling

7
我的解决方案如下。您可以在此页面的第二个示例中找到函数http_digest_parse$realm$usershttp://php.net/manual/en/features.http-auth.php。请注意,保留了HTML标签。
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;
}

7

两步操作退出HTTP基本认证

假设我有一个名为“密码保护”的HTTP基本认证领域,并且Bob已经登录。要退出,我需要进行2个AJAX请求:

  1. 访问脚本/logout_step1。它将在.htusers中添加一个随机的临时用户,并返回其登录名和密码。
  2. 使用临时用户的登录名和密码访问脚本/logout_step2(已经通过身份验证)。该脚本将删除临时用户并在响应上添加此标头:WWW-Authenticate: Basic realm="Password protected"

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


1
哇!这真的值得+1,因为它非常有创意,即使这是一件完全疯狂的事情。 - Andy Triggs

5

我发现清除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');
}

4

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


我相信在Firefox中选择“删除私人数据”时,有一个选项可以删除已认证的会话。 - Kristian J.
1
另外,Firefox 的 Web 开发者工具栏扩展提供了删除 HTTP 认证的功能。但这是不可能的,因为我们不能要求用户下载 Firefox 扩展或运行神秘的浏览器命令 :-) - Josef Sábl
2
Firefox的HTTP身份验证默认注销方式可在“工具”>“清除最近的历史记录…”下找到,作为复选框“活动登录”。这既不直观,也不能让您仅注销一个域,您总是注销每个页面。 - aef

2

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

  • 这是HTTP身份验证机制本身的问题,我们在Trac中无法进行适当的修复。
  • 目前没有任何解决方法(JavaScript或其他),适用于所有主流浏览器。

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

看起来似乎没有对这个问题的有效答案,这个问题已经报告了七年,这是有道理的:HTTP是无状态的。请求要么带有身份验证凭据,要么没有。但这取决于客户端发送请求,而不是服务器接收请求。服务器只能说明请求URI是否需要授权。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接