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

发布于 2024-07-12 03:04:20 字数 139 浏览 2 评论 0原文

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

有一些解决方法可以实现此目的,但它们具有潜在的危险,因为它们可能存在错误或在某些情况/浏览器中不起作用。 这就是为什么我正在寻找正确且干净的解决方案。

What is the correct way to log out of HTTP authentication protected folder?

There are workarounds that can achieve this, but they are potentially dangerous because they can be buggy or don't work in certain situations / browsers. That is why I am looking for correct and clean solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

秋日私语 2024-07-19 03:04:20

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

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

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

另一方面,10.4.2 部分说:

如果请求已包含授权凭据,则 401
响应表明授权已被拒绝
证书。 如果 401 响应包含与
事先响应,并且用户代理已经尝试过
至少进行一次身份验证,然后应该向用户提供
响应中给出的实体,因为该实体可能
包括相关的诊断信息。

换句话说,您可以再次显示登录框(如@Karsten 说),但是浏览器不必满足您的请求 - 所以不要过度依赖这个(错误的)功能。

Mu. No correct way exists, not even one that's consistent across browsers.

This is a problem that comes from the HTTP specification (section 15.6):

Existing HTTP clients and user agents typically retain authentication
information indefinitely. HTTP/1.1. does not provide a method for a
server to direct clients to discard these cached credentials.

On the other hand, section 10.4.2 says:

If the request already included Authorization credentials, then the 401
response indicates that authorization has been refused for those
credentials. If the 401 response contains the same challenge as the
prior response, and the user agent has already attempted
authentication at least once, then the user SHOULD be presented the
entity that was given in the response, since that entity might
include relevant diagnostic information.

In other words, you may be able to show the login box again (as @Karsten says), but the browser doesn't have to honor your request - so don't depend on this (mis)feature too much.

与酒说心事 2024-07-19 03:04:20

在 Safari 中效果很好的方法。 也适用于 Firefox 和 Opera,但有警告。

Location: http://[email protected]/

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

Method that works nicely in Safari. Also works in Firefox and Opera, but with a warning.

Location: http://[email protected]/

This tells browser to open URL with new username, overriding previous one.

彼岸花似海 2024-07-19 03:04:20

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

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

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

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

是的,有一些杂凑。 但它们破坏了 RESTful-ness(如果这对您有价值)并且它们是不可靠的。

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

The simple answer is that you can't reliably log out of http-authentication.

The long answer:
Http-auth (like the rest of the HTTP spec) is meant to be stateless. So being "logged in" or "logged out" isn't really a concept that makes sense. The better way to see it is to ask, for each HTTP request (and remember a page load is usually multiple requests), "are you allowed to do what you're requesting?". The server sees each request as new and unrelated to any previous requests.

Browsers have chosen to remember the credentials you tell them on the first 401, and re-send them without the user's explicit permission on subsequent requests. This is an attempt at giving the user the "logged in/logged out" model they expect, but it's purely a kludge. It's the browser that's simulating this persistence of state. The web server is completely unaware of it.

So "logging out", in the context of http-auth is purely a simulation provided by the browser, and so outside the authority of the server.

Yes, there are kludges. But they break RESTful-ness (if that's of value to you) and they are unreliable.

If you absolutely require a logged-in/logged-out model for your site authentication, the best bet is a tracking cookie, with the persistence of state stored on the server in some manner (mysql, sqlite, flatfile, etc). This will require all requests to be evaluated, for instance, with PHP.

痴情 2024-07-19 03:04:20

解决方法

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

Workaround

You can do this using 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>

What is done above is:

  • for IE - just clear auth cache and redirect somewhere

  • for other browsers - send an XMLHttpRequest behind the scenes with 'logout' login name and password. We need to send it to some path that will return 200 OK to that request (i.e. it shouldn't require HTTP authentication).

Replace '/where/to/redirect' with some path to redirect to after logging out and replace '/path/that/will/return/200/OK' with some path on your site that will return 200 OK.

奢望 2024-07-19 03:04:20

解决方法(不是一个干净、漂亮(甚至有效!请参阅评论)的解决方案):

禁用他的凭据一次。

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

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

Workaround (not a clean, nice (or even working! see comments) solution):

Disable his credentials one time.

You can move your HTTP authentication logic to PHP by sending the appropriate headers (if not logged in):

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

And parsing the input with:

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

So disabling his credentials one time should be trivial.

樱花坊 2024-07-19 03:04:20

分两步从 HTTP 基本身份验证注销

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

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

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

Logout from HTTP Basic Auth in two steps

Let’s say I have a HTTP Basic Auth realm named “Password protected”, and Bob is logged in. To log out I make 2 AJAX requests:

  1. Access script /logout_step1. It adds a random temporary user to .htusers and responds with its login and password.
  2. Access script /logout_step2 authenticated with the temporary user’s login and password. The script deletes the temporary user and adds this header on the response: WWW-Authenticate: Basic realm="Password protected"

At this point browser forgot Bob’s credentials.

不知在何时 2024-07-19 03:04:20

我对这个问题的解决方案如下。 您可以在本页的第二个示例中找到函数 http_digest_parse$realm$usershttp://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;
}

My solution to the problem is the following. You can find the function http_digest_parse , $realm and $users in the second example of this page: 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;
}
川水往事 2024-07-19 03:04:20

我发现清除 PHP_AUTH_DIGESTPHP_AUTH_USER AND PHP_AUTH_PW 凭据的唯一有效方法是调用标头 HTTP/1.1 401 未经授权。

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

The only effective way I've found to wipe out the PHP_AUTH_DIGEST or PHP_AUTH_USER AND PHP_AUTH_PW credentials is to call the header HTTP/1.1 401 Unauthorized.

function clear_admin_access(){
    header('HTTP/1.1 401 Unauthorized');
    die('Admin access turned off');
}
芸娘子的小脾气 2024-07-19 03:04:20

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

Typically, once a browser has asked the user for credentials and supplied them to a particular web site, it will continue to do so without further prompting. Unlike the various ways you can clear cookies on the client side, I don't know of a similar way to ask the browser to forget its supplied authentication credentials.

绻影浮沉 2024-07-19 03:04:20

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

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

发件人: http://trac.edgewall.org/ticket/ 791#comment:103

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

Trac - by default - uses HTTP Authentication as well. Logout does not work and can not be fixed:

  • This is an issue with the HTTP authentication scheme itself, and there's nothing we can do in Trac to fix it properly.
  • There is currently no workaround (JavaScript or other) that works with all major browsers.

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

Looks like that there is no working answer to the question, that issue has been reported seven years ago and it makes perfect sense: HTTP is stateless. Either a request is done with authentication credentials or not. But that's a matter of the client sending the request, not the server receiving it. The server can only say if a request URI needs authorization or not.

猫九 2024-07-19 03:04:20

我需要重置 .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,不像其他浏览器那样清除身份验证;)

我在我安装的浏览器上测试了它,一旦关闭,每个浏览器似乎始终需要重新身份验证再入。

I needed to reset .htaccess authorization so I used this:

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

Found it here :
http://php.net/manual/en/features.http-auth.php

Go figure.

A number of solutions reside on that page and it even notes at the bottom: Lynx, doesn't clear the auth like other browsers ;)

I tested it out on my installed browsers and once closed, each browser seems like it consistently requires reauth on reentry.

佞臣 2024-07-19 03:04:20

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

logout.php

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

log.php

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

这样我就不会收到警告并且我的会话被终止

This might be not the solution that was looked for but i solved it like this.
i have 2 scripts for the logout process.

logout.php

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

log.php

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

This way i dont get a warning and my session is terminated

狼亦尘 2024-07-19 03:04:20

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

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

AFAIK, there's no clean way to implement a "logout" function when using htaccess (i.e. HTTP-based) authentication.

This is because such authentication uses the HTTP error code '401' to tell the browser that credentials are required, at which point the browser prompts the user for the details. From then on, until the browser is closed, it will always send the credentials without further prompting.

哭泣的笑容 2024-07-19 03:04:20

到目前为止我发现的最好的解决方案是(它是一种伪代码,$isLoggedIn是http身份验证的伪变量):

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

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

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

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

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

The best solution I found so far is (it is sort of pseudo-code, the $isLoggedIn is pseudo variable for http auth):

At the time of "logout" just store some info to the session saying that user is actually logged out.

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

In the place where I check for authentication I expand the condition:

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

Session is somewhat linked to the state of http authentication so user stays logged out as long as he keeps the browser open and as long as http authentication persists in the browser.

扶醉桌前 2024-07-19 03:04:20

也许我没有抓住要点。

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

Maybe I'm missing the point.

The most reliable way I've found to end HTTP Authentication is to close the browser and all browser windows. You can close a browser window using Javascript but I don't think you can close all browser windows.

大姐,你呐 2024-07-19 03:04:20

虽然其他人正确地说不可能从基本 http 身份验证注销,但有一些方法可以实现行为类似的身份验证。 一种明显的方法是使用 auth_memcookie。 如果您确实想使用此功能实现基本 HTTP 身份验证(即使用浏览器对话框而不是 HTTP 表单进行登录) - 只需将身份验证设置为单独的 .htaccess 受保护目录,其中包含 PHP 脚本,该脚本重定向回用户之后的位置创建内存缓存会话。

While the others are correct in saying that its impossible to logout from basic http authentication there are ways to implement authentication which behave similarly. One obvious appeoach is to use auth_memcookie. If you really want to implement Basic HTTP authentication (i.e. use the browser dialogs for logging in trather than an HTTP form) using this - just set the authentication to a seperate .htaccess protected directory containing a PHP script which redirects back where te user came after createing the memcache session.

动次打次papapa 2024-07-19 03:04:20

这里有很多伟大而复杂的答案。 在我的特殊情况下,我找到了一个干净而简单的注销修复方法。 我还没有在 Edge 中进行测试。
在我登录的页面上,我放置了一个与此类似的注销链接:

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

在该 logout.html 页面(也受 .htaccess 保护)的头部,我有一个与此类似的页面刷新

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

:您可以保留“注销”一词,以清除为站点缓存的用户名和密码。

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

There's a lot of great - complex - answers here. In my particular case i found a clean and simple fix for the logout. I have yet to test in Edge.
On my page that I have logged in to, I have placed a logout link similar to this:

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

And in the head of that logout.html page (which is also protected by the .htaccess) I have a page refresh similar to this:

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

Where you would leave the words "logout" in place to clear the username and password cached for the site.

I will admit that if multiple pages needed to be able to be directly logged in to from the beginning, each of those points of entry would need their own corresponding logout.html page. Otherwise you could centralize the logout by introducing an additional gatekeeper step into the process before the actual login prompt, requiring entry of a phrase to reach a destination of login.

掩于岁月 2024-07-19 03:04:20

我在一篇文章中总结了我的解决方案(https://www.hattonwebsolutions.co.uk/articles /how_to_logout_of_http_sessions)但是我使用了ajax调用和2x htaccess文件(正如这个问题中所建议的:如何注销HTTP身份验证(htaccess) 可以在 Google Chrome 中运行吗?)。

简而言之 - 您:

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

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

I have summarised my solution in an article (https://www.hattonwebsolutions.co.uk/articles/how_to_logout_of_http_sessions) however I have used an ajax call and 2x htaccess files (as suggested in this question: How to logout of an HTTP authentication (htaccess) that works in Google Chrome?).

In short - you:

  1. Create a sub folder with an htaccess file on the same AuthName but require a different user
  2. Send an ajax request to the page (with the wrong username) (which fails) and then trigger a timeout redirect to the logged out page.

This avoids having a secondary popup in the logout folder requesting another username (which would confuse users). My article uses Jquery but it should be possible to avoid this.

别忘他 2024-07-19 03:04:20

我通过发送 401 标头实现了这一点。 这很好用。
使用 PHP

header('HTTP/1.0 401 Unauthorized');

I achieved this with sending a 401 header. This works fine.
With PHP

header('HTTP/1.0 401 Unauthorized');
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文