基于PHP、MySQL的登录系统存在问题

基于PHP、MySQL的登录系统存在问题,php,security,login-script,Php,Security,Login Script,重要 按照下面答案中的建议进行操作后,客户端可以毫无问题地登录,但没有尝试实际浏览受保护的页面。当他稍后尝试这样做时,返回到与以前一样的登录,并出现“请登录”错误。经过一番努力之后,我想到了一件非常简单的事情——客户端正在使用登录脚本访问站点,并且登录脚本中的所有内容都被重定向到,因此它正在寻找的会话cookie被设置为另一个域。这也解释了为什么他在第一次登录时遇到问题,但在随后的几次登录中没有出现问题——脚本将他重定向到登录表单,而没有www 一个快速修复方法是编写一个.htaccess文件来

重要

按照下面答案中的建议进行操作后,客户端可以毫无问题地登录,但没有尝试实际浏览受保护的页面。当他稍后尝试这样做时,返回到与以前一样的登录,并出现“请登录”错误。经过一番努力之后,我想到了一件非常简单的事情——客户端正在使用登录脚本访问站点,并且登录脚本中的所有内容都被重定向到,因此它正在寻找的会话cookie被设置为另一个域。这也解释了为什么他在第一次登录时遇到问题,但在随后的几次登录中没有出现问题——脚本将他重定向到登录表单,而没有www

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

原创帖子

我使用自制CMS和登录系统开发基于PHP和MySQL的网站。我的CMS对每一个客户都是独一无二的,而且它一直都很讨人喜欢——不幸的是,我的登录系统并非如此。下面是一篇很长的帖子,但我需要讲述细节,以尝试找到解决方案。请容忍我

这个系统是相当直接的,如果不是有点大的话。每个用户都有一个SALT散列存储在MySQL表中,与SALT一起。当用户登录时,将检索他们的SALT,提交的密码将成为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();
        }

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

我突然想到的一件事是:

header('Location: http://example.com/members');
session_write_close();
exit();
我会将
会话\u write\u close()
调用放在
标题('location…')之前。

日志中是否显示任何“已发送邮件头”错误


我想到的另一件事是一些AJAX比赛条件。登录页面是否有异步调用?

我突然想到的一件事是:

header('Location: http://example.com/members');
session_write_close();
exit();
我会将
会话\u write\u close()
调用放在
标题('location…')之前。

日志中是否显示任何“已发送邮件头”错误


我想到的另一件事是一些AJAX比赛条件。登录页面是否有异步调用?

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


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

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


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

成功!仍然需要缩小导致问题g的确切变化范围