重要
按照以下答案中的建议后,客户端能够毫无问题地登录,但没有尝试实际导航安全页面。当他后来尝试这样做时,他像以前一样返回登录“请登录”错误。经过多次努力之后,想到了一些非常简单的东西 - 客户端正在使用http://www.example.com/admin访问该网站,并且登录脚本中的所有内容都重定向到http://example.com,因此设置的会话cookie已设置对于另一个域名。这也解释了为什么他第一次登录而不是后续登录时出现问题 - 脚本将他重定向到没有www的登录表单。
快速修复是写一个.htaccess文件来删除www,问题解决了。当然,这也可以在登录脚本中处理,我将改进以供将来使用。
原始帖子
我使用家庭酿造CMS和登录系统开发基于PHP和MySQL的站点。我的CMS对每个客户来说都是独一无二的,而且很受欢迎 - 不幸的是我的登录系统并不是这样。以下是一篇很长的文章,但我需要详细介绍以尝试找到解决方案。忍受我..
系统相当直接,如果不是有点大。每个用户都有一个salted哈希存储在MySQL表中,与SALT一起存储。当用户登录时,将检索其SALT,并且提交的密码将成为盐渍哈希。
如果提交的盐渍哈希与表中存储的哈希匹配,则对用户进行身份验证。其名称,最后IP地址和帐户级别(大多数站点上的3个级别)等详细信息存储在分配给会话变量的阵列中。然后将它们重定向到他们登录的受限制站点的登录页面(仅限成员或管理员/ CMS)。
安全页面包含一个较小的auth.php文件,该文件检查是否存在包含其详细信息的会话变量。如果没有,它们将被重定向到该站点的登录表单,并显示一条错误消息“请登录”。如果存在,则允许它们继续,并将存储在数组中的详细信息分配给变量。
许多用户报告的问题是他们经常需要多次登录才能避免被“请登录”错误消息退回到登录表单,或者他们导航到另一个页面在安全站点中,随机退回到登录时出现同样的错误。因此,会话变量似乎要么没有设置,要么在正常使用站点时因某些原因被清除。
第一个问题从来没有发生在我身上 - 在众多设备和网络上 - 我在客户办公室使用他们的笔记本电脑见证了这个问题。我让他们连接到我的移动热点,没有任何变化。但是,使用我的笔记本电脑和热点连接,他们能够毫无问题地登录。不幸的是,我无法使用笔记本电脑连接到他们的网络,因此无法排除变量。
* 注意 - * 我最初忘了提到系统在问题客户端使用正确的凭据登录两到三次后正常工作。其浏览器保持打开状态的后续登录尝试往往会在没有问题的情况下执行。此外,登录页面会破坏会话。
以下是每个阶段的代码,从登录脚本开始:
的login.php
<?php
putenv("TZ=US/Eastern");
if (array_key_exists('site', $_POST)) {
$authenticate = new loginUser($_POST['username'], $_POST['password'], $_POST['site'], $_SERVER['REMOTE_ADDR']);
}
//Authenticate and log-in
class loginUser {
private $memDB, $username, $password, $site, $ip_address;
//Clean input variables
private function clean($str) {
$str = @trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return $str;
}
//Construct variables
function __construct($username, $password, $site, $ip_address) {
session_start();
$this->memDB = new PDO('mysql:host=localhost;dbname=exampleDB', 'exampleUser', 'examplePassword');
$this->username = $this->clean($username);
$this->password = $this->clean($password);
$this->site = $site;
$this->ip_address = $ip_address;
$this->authUser();
}
//Validate username
private function validateUsername($username) {
$checkUsername = $this->memDB->prepare("SELECT COUNT(*) FROM accounts WHERE username = ?");
$checkUsername->execute(array($username));
return $checkUsername->fetchColumn();
}
//Obtain and set account details
private function accountDetails() {
$fetchAccountDetails = $this->memDB->prepare("SELECT id, name_f, name_l, ipAddr, lastLogin, accountLevel, isActive
FROM accounts WHERE username = ?");
$fetchAccountDetails->execute(array($this->username));
$accountDetails = $fetchAccountDetails->fetch();
$this->updateLogin();
return $accountDetails;
}
//Update last login details
private function updateLogin() {
$updateLogin = $this->memDB->prepare("UPDATE accounts SET ipAddr = ?, lastLogin = DATE_ADD(NOW(), INTERVAL 1 HOUR) WHERE username = ?");
$updateLogin->execute(array($this->ip_address, $this->username));
}
public function authUser() {
$loginErr = array(); //Array for holding login error message
$loginErrFlag = false; //Boolean for error
//Validate submitted $_POST elements
if (!$this->username) {
$loginErr[] = "Username missing";
$loginErrFlag = true;
}
if (!$this->password) {
$loginErr[] = "Password missing";
$loginErrFlag = true;
}
if ($this->username && $this->validateUsername($this->username) == 0) {
$loginErr[] = "Username invalid";
$loginErrFlag = true;
}
if (!$loginErrFlag) {
//Fetch the password and SALT to compare to entered password
$validatePW = $this->memDB->prepare("SELECT password, salt FROM accounts WHERE username = ? LIMIT 1");
$validatePW->execute(array($this->username));
$passwordResult = $validatePW->fetch();
$dbPW = $passwordResult['password'];
$dbSalt = $passwordResult['salt'];
//Compare entered password to SALT + hash
$hashPW = hash('sha512', $dbSalt . $this->password);
if ($hashPW === $dbPW) {
//Logged in
$_SESSION['CVFD-USER-DETAILS'] = $this->accountDetails();
//Redirect to secure landing page for log-in origin (Members or Admin)
//Adding SID is a recent attempt to handle log-in problems
header("Location: http://example.com/$this->site/$this->site-main.php?" . SID);
//session_write_close() was here but was removed
exit();
} else {
//Password invalid
$loginErr[] = "Please check your password and try again";
$_SESSION['CVFD_LOGIN_ERR'] = $loginErr;
//Redirect to the log-in for the origin
header("Location: http://example.com/$this->site");
session_write_close();
exit();
}
} else {
$_SESSION['CVFD_LOGIN_ERR'] = $loginErr;
header("Location: http://example.com/$this->site");
session_write_close();
exit();
}
}
}
?>
auth.php
<?php
session_start();
if (!isset($_SESSION['CVFD-USER-DETAILS']) || $_SESSION['CVFD-USER-DETAILS'] == '') {
//Not logged in
$_SESSION['CVFD_LOGIN_ERR'] = array('Please login');
header('Location: http://example.com/members');
session_write_close();
exit();
} else {
$userDetails = $_SESSION['CVFD-USER-DETAILS']; //Assign user details array to variable
//Check to see if account is active
$accountStatus = $userDetails['isActive'];
$accountLevel = $userDetails['accountLevel'];
if ($accountStatus == 0) {
//Account is not yet active (pending Admin activation)
$_SESSION['CVFD_LOGIN_ERR'] = array('Your account is suspended or pending activation');
header('Location: http://example.com/members');
session_write_close();
exit();
} else {
$CVFDFirstName = $userDetails['name_f'];
$CVFDLastName = $userDetails['name_l'];
$CVFDLastLogin = date("m/d/Y H:i:s", strtotime($userDetails['lastLogin']));
$CVFDAccountLevel = $userDetails['accountLevel'];
$CVFDIPAddr = $userDetails['ipAddr'];
}
}
?>
以下是auth.php如何包含在安全文件中 -
<?php
if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); else ob_start();
require($_SERVER['DOCUMENT_ROOT'] . '/members/includes/handlers/handler.auth.php');
任何帮助将不胜感激。相当神秘......
谢谢!
答案 0 :(得分:1)
我登录系统的方式是只使用会话ID,而不是在会话本身中存储任何内容。当用户登录他们的散列用户代理数据时,他们的会话ID,他们的用户ID(对应于用户表)和到期时间被放入一个表,通常称为“active_users”,然后我有一个登录的头文件包括在每个启动会话的管理员受限页面中,检索用户会话ID并检查该会话ID是否在活动用户表中,以及检查的用户是否具有相同的用户代理数据,不会超过到期时间。如果该查询没有返回任何内容,则表示他们未登录并退回。
这就是我工作的大多数登录系统的工作方式,我没有遇到任何问题。
答案 1 :(得分:0)
向我跳出的一件事是:
header('Location: http://example.com/members');
session_write_close();
exit();
我会在session_write_close()
header('location ...')
来电
您的日志中是否显示了“已发送标头”错误?
其他想到的是一些AJAX竞争条件。是否有任何与登录页面同步的异步调用?
答案 2 :(得分:0)
成功!仍然需要缩小究竟是什么变化导致问题消失,但客户报告他不再有登录问题。
立即浮现在脑海中的最大变化就是在任何地方删除session_write_close()
。它可能是在代码的某些部分中的标题重定向之后放置的,或者只是将其存在可能是原因。我将尝试在重定向之前放置它。
感谢大家的建议