注销HTTP身份验证保护文件夹的正确方法是什么?
有一些解决方法可以实现这一点,但它们具有潜在的危险性,因为它们可能是错误的,或者在某些情况/浏览器中不起作用.这就是为什么我正在寻找正确和干净的解决方案.
亩.没有正确的方法,甚至没有一个跨浏览器一致的方式.
这是来自HTTP规范的问题(第15.6节):
现有的HTTP客户端和用户代理通常会无限期地保留身份验证信息.HTTP/1.1.没有提供服务器指示客户端丢弃这些缓存凭据的方法.
另一方面,第10.4.2节说:
如果请求已包含授权凭据,则401响应表示已拒绝授权这些凭据.如果401响应包含与先前响应相同的挑战,并且用户代理已经尝试过至少一次认证,则应该向用户呈现响应中给出的实体,因为该实体可能包括相关的诊断信息.
换句话说,您可以再次显示登录框(如@Karsten所说),但浏览器不必遵守您的请求 - 因此不要过多依赖此(错误)功能.
在Safari中很好地工作的方法.也适用于Firefox和Opera,但有警告.
Location: http://logout@yourserver.example.com/
这告诉浏览器使用新用户名打开URL,覆盖前一个用户名.
简单的答案是您无法可靠地注销http身份验证.
答案很长:
Http-auth(与HTTP规范的其余部分一样)意味着无状态.因此,"登录"或"退出"并不是一个有意义的概念.更好的方法是询问每个HTTP请求(并记住页面加载通常是多个请求),"你被允许做你要求的吗?".服务器将每个请求视为新请求,与先前的任何请求无关.
浏览器选择记住您在第一个401上告诉他们的凭据,并在没有用户对后续请求的明确许可的情况下重新发送它们.这是为了向用户提供他们期望的"登录/注销"模型,但它纯粹是一种kludge.这是模拟这种状态持久性的浏览器.Web服务器完全没有意识到它.
因此,在http-auth的上下文中"注销"纯粹是由浏览器提供的模拟,因此在服务器的权限之外.
是的,有kludges.但是他们打破了RESTful-ness(如果这对你有价值的话)并且它们是不可靠的.
如果您绝对需要登录/注销模型进行站点身份验证,最好的选择是跟踪cookie,状态的持久性以某种方式存储在服务器上(mysql,sqlite,flatfile等).这将要求评估所有请求,例如,使用PHP.
解决方法
你可以使用Javascript来做到这一点:
Log out
上面做的是:
对于IE - 只需清除身份验证缓存并重定向到某个地方
对于其他浏览器 - 使用'logout'登录名和密码在幕后发送XMLHttpRequest.我们需要将它发送到某个路径,该路径将向该请求返回200 OK(即它不应该要求HTTP身份验证).
'/where/to/redirect'
在注销后替换为某些路径以重定向到并替换'/path/that/will/return/200/OK'
为您的站点上将返回200 OK的某个路径.
解决方法(不是干净,漂亮(甚至工作!请参阅注释)解决方案):
一次禁用他的凭据.
您可以通过发送相应的标头(如果未登录)将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
因此,一次禁用他的凭据应该是微不足道的.
我对此问题的解决方案如下.你可以找到的功能http_digest_parse
,$realm
并$users
在此页的第二个例子: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; }
假设我有一个名为"密码保护"的HTTP Basic Auth领域,并且Bob已登录.要注销,我发出2个AJAX请求:
访问脚本/ logout_step1.它会向.htusers添加一个随机临时用户,并使用其登录名和密码进行响应.
访问脚本/ logout_step2 使用临时用户的登录名和密码进行身份验证.该脚本删除临时用户并在响应中添加此标头:WWW-Authenticate: Basic realm="Password protected"
此时浏览器忘记了Bob的凭据.