1

重要的

在遵循以下答案中的建议后,客户能够毫无问题地登录,但并未尝试实际导航受保护的页面。当他稍后尝试这样做时,他又像以前一样重新登录,并出现“请登录”错误。经过一番摸索,我想到了一些非常简单的事情 - 客户端正在使用http://www.example.com/admin访问该站点,并且登录脚本中的所有内容都重定向到http://example.com,因此会话它正在寻找的 cookie 是为另一个域设置的。这也解释了为什么他在第一次登录时遇到问题,但后来却没有——脚本将他重定向到没有 www 的登录表单。

一个快速的解决方法是编写一个 .htaccess 文件来删除 www,问题解决了。当然,这也可以在登录脚本中处理,我将对其进行改进以备将来使用。

原帖

我使用自制的 CMS 和登录系统开发基于 PHP 和 MySQL 的站点。我的 CMS 对每个客户来说都是独一无二的,而且很受大众欢迎 - 不幸的是,我的登录系统并非如此。以下是一篇很长的文章,但我需要介绍详细信息以尝试找到解决方案。忍受我..

该系统相当简单,即使不是有点沉重。每个用户在 SALT 旁边都有一个存储在 MySQL 表中的盐渍哈希。当用户登录时,他们的 SALT 被检索并且提交的密码变成了一个加盐哈希。

如果提交的加盐哈希与存储在表中的哈希匹配,则用户通过身份验证。他们的姓名、最后一个 IP 地址和帐户级别(大多数站点上为 3 个级别)等详细信息存储在分配给会话变量的数组中。然后他们被重定向到他们登录的受限站点的登录页面(仅限会员或管理员/CMS)。

安全页面包括一个较小的 auth.php 文件,该文件检查是否存在包含其详细信息的会话变量。如果没有,他们将被重定向到该站点的登录表单,并显示一条错误消息,内容为“请登录”。如果存在,则允许它们继续,并将存储在数组中的详细信息分配给变量。

许多用户反映的问题是,他们经常需要多次登录,以免被弹回登录表单并显示“请登录”错误消息,或者他们导航到安全中的另一个页面网站并随机返回登录并出现相同的错误。因此,会话变量似乎要么没有被设置,要么在正常使用站点期间由于某种原因被清除。

第一个问题从未发生在我身上——在众多设备和网络上——我在客户办公室使用他们的笔记本电脑亲眼目睹了它。我让他们连接到我的移动热点,但没有任何变化。但是,他们能够使用我的笔记本电脑和热点连接毫无问题地登录。不幸的是,我无法使用我的笔记本电脑连接到他们的网络,因此不能排除这个变量。

*注意 - *我最初忘记提到,在问题客户使用正确的凭据登录两到三次后,系统可以正常工作。在他们的浏览器保持打开状态的情况下进行的后续登录尝试往往会在此后毫无问题地执行。此外,登录页面会破坏会话。

以下是每个阶段的代码,从登录脚本开始:

登录.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();
        }

    }
}
?>

授权文件

<?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');

任何帮助,将不胜感激。真是个谜。。

谢谢!

4

3 回答 3

1

我登录系统的方式是只使用会话 ID,而不是在会话本身中存储任何内容。当用户登录他们的散列用户代理数据时,他们的会话 ID、他们的用户 ID(对应于用户表)和到期时间被放入一个表中,通常称为“active_users”,然后我有一个包含的登录头文件在每个启动会话的管理员受限页面中,检索用户会话 ID 并检查该会话 ID 是否在活动用户表中,以及被检查的用户是否具有相同的用户代理数据,不超过到期时间。如果该查询没有返回任何内容,则他们没有登录并被退回。

这就是我制作的大多数登录系统的工作方式,而且我没有遇到任何问题。

于 2012-10-05T16:00:46.613 回答
0

让我跳出来的一件事是:

header('Location: http://example.com/members');
session_write_close();
exit();

我会session_write_close()header('location ...')

您的日志中是否显示任何“标头已发送”错误?

想到的另一件事是一些 AJAX 竞争条件。登录页面正在进行任何异步调用吗?

于 2012-10-05T15:53:16.513 回答
0

成功!仍然需要准确缩小导致问题消失的更改范围,但客户报告说他不再有登录问题。

立即想到的最大变化是session_write_close()几乎无处不在。它可能被放置在部分代码中的标头重定向之后,或者只是让它存在可能是原因。我将尝试在重定向之前放置它。

感谢大家的建议

于 2012-10-05T17:44:17.660 回答