简体   繁体   English

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

[英]Problems with PHP, MySQL based log-in system

IMPORTANT 重要

After following the advice in the answers below, the client was able to log-in without any problems but did not attempt to actually navigate the secured pages. 遵循以下答案中的建议后,客户端可以登录而没有任何问题,但没有尝试实际浏览受保护的页面。 When he attempted to do so later, he was returned to log-in as before with the "Please log in" error. 当他稍后尝试这样做时,由于出现“请登录”错误,他像以前一样返回登录状态。 After much head scratching, something incredibly simple came to mind - the client was accessing the site with http://www.example.com/admin and everything in the login script was redirecting to http://example.com , so the session cookie that it was looking for was set for another domain. 经过一番摸索之后,想到了一个非常简单的事情-客户端使用http://www.example.com/admin访问该网站,并且登录脚本中的所有内容都重定向到http://example.com ,因此会话它正在寻找的cookie是为另一个域设置的。 This also explains why he had problems logging in the first time but not subsequent times - the script redirected him to the log-in form without the www. 这也解释了为什么他第一次登录时遇到问题,但随后没有登录-脚本将他重定向到没有www的登录表单。

A quick fix was to write a .htaccess file to remove the www, problem solved. 一个快速的解决方法是编写一个.htaccess文件来删除www,此问题已解决。 Of course this could also be handled within the login script, which I will improve for future use. 当然,这也可以在登录脚本中处理,我将对其进行改进以备将来使用。

ORIGINAL POST 原始邮件

I develop PHP and MySQL based sites with a home brew CMS and log-in system. 我使用家庭自用CMS和登录系统开发基于PHP和MySQL的站点。 My CMS is unique to every client and it has been quite the crowd pleaser - unfortunately the same is not true of my log-in system. 我的CMS对于每个客户来说都是唯一的,并且非常受欢迎-不幸的是,我的登录系统并非如此。 The following is a long post but I need to cover the details to try and find a solution. 以下是一篇很长的文章,但我需要涵盖所有细节才能尝试找到解决方案。 Bear with me.. 忍受我..

The system is fairly straight forward, if not a bit hefty. 该系统相当简单,即使有些沉重。 Each user has a salted hash stored in a MySQL table, alongside the SALT. 每个用户的SALT旁边都有一个存储在MySQL表中的盐腌哈希。 When the user logs in, their SALT is retrieved and the submitted password becomes a salted hash. 当用户登录时,将检索其SALT,并且所提交的密码将变成含盐散列。

If the submitted salted hash matches the one stored in the table, the user is authenticated. 如果提交的加盐哈希与表中存储的哈希匹配,则对用户进行身份验证。 Details such as their name, last IP address, and account level (3 levels on most sites) are stored in an array assigned to a session variable. 诸如名称,最后IP地址和帐户级别(大多数站点上为3个级别)之类的详细信息存储在分配给会话变量的数组中。 They are then redirected to the landing page of the restricted site to which they logged in (Members Only or Admin/CMS). 然后,他们将重定向到他们登录到的受限站点的登录页面(“仅限会员”或“ Admin / CMS”)。

Secured pages include a smaller auth.php file that checks to see if the session variable containing their details is present. 受保护的页面包括一个较小的auth.php文件,该文件检查是否存在包含其详细信息的会话变量。 If not, they are redirected to that site's log-in form with an error message that reads "Please log-in." 如果不是,它们将被重定向到该站点的登录表单,并显示一条错误消息“请登录”。 If it is present, they are allowed to continue and the details stored in the array are assigned to variables. 如果存在,则允许它们继续,并将数组中存储的详细信息分配给变量。

The problem that many users has reported is that they often need to log-in several times to keep from being bounced back to the log-in form with the "Please log-in" error message, or they navigate to another page in the secure site and randomly get bounced back to login with the same error. 许多用户报告的问题是,他们经常需要登录几次才能避免出现带有“请登录”错误消息的跳回登录表单,或者他们导航到安全的另一个页面站点并随机跳回以相同的错误登录。 So, the session variable seems to either not be getting set or it is being cleared for some reason during normal use of the site. 因此,在正常使用站点期间,会话变量似乎未设置或由于某种原因而被清除。

The first problem has NEVER happened to me - over a multitude of devices and networks - and I have witnessed it at a client's office using their laptop. 我的第一个问题从来没有发生过-在许多设备和网络上-我已经在使用笔记本电脑的客户办公室目睹了这一问题。 I had them connect to my mobile hotspot and there was no change. 我让他们连接到了我的移动热点,并且没有任何变化。 However, they were able to log in without any problems using my laptop and my hotspot connection. 但是,他们可以使用我的笔记本电脑和热点连接登录而没有任何问题。 Unfortunately, I was not able to connect to their network using my laptop, so that variable could not be ruled out. 不幸的是,我无法使用我的笔记本电脑连接到他们的网络,因此无法排除该变量。

* NOTE - * I forgot to mention initially that the system works normally for problem clients after they have logged in two or three times with the correct credentials. * 注意-*我最初忘记提及问题客户端使用正确的凭据登录两次或三次后,系统可以正常运行。 Subsequent log-in attempts while their browser remains open tend to execute without problems thereafter. 在浏览器保持打开状态的情况下进行后续登录尝试,此后往往不会出现问题。 Also, the log-in page destroys the session. 同样,登录页面会破坏会话。

Here is the code for each stage, starting with the log-in script: 这是每个阶段的代码,从登录脚本开始:

login.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 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'];
    }
}
?>

Here is how the auth.php is included in secure files- 这是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');

Any help would be appreciated. 任何帮助,将不胜感激。 Quite a mystery.. 相当神秘。

Thanks! 谢谢!

The way I do login system is to just use the session id, rather than storing anything in the session itself. 我登录系统的方法是仅使用会话ID,而不是在会话本身中存储任何内容。 When a user logs in their hashed user agent data, their session ID, their user id (corresponding to a users table) and an expiry time is put into a table, often called "active_users", I then had a logged in head file included in every admin restricted page that starts the session, retrieves the users session ID and checks to see whether that session ID is in the active users table and whether the user being checked against has the same user agent data, the expiry time is not surpassed. 当用户登录其散列的用户代理数据,其会话ID,其用户ID(与用户表相对应)和到期时间进入表(通常称为“ active_users”)时,我便拥有一个已登录的头文件在启动会话的每个受管理限制的页面中,检索用户会话ID并检查该会话ID是否在活动的用户表中,以及要检查的用户是否具有相同的用户代理数据,不会超过到期时间。 If nothing is returned from that query they're not logged in and are bounced out. 如果该查询未返回任何内容,则说明它们未登录并被退回。

That's how most login systems I make work and I haven't had any problems. 这就是我使用大多数登录系统的方式,但是我没有任何问题。

The one thing that jumps out at me is the following: 跳到我身上的一件事是:

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

I would place the session_write_close() call before the header('location ...') 我将session_write_close()调用放在header('location ...')

Are any 'headers already sent' errors showing up in your logs? 日志中是否显示任何“已发送标题”错误?

Other thing that comes to mind is some AJAX race condition. 我想到的另一件事是某些AJAX竞争条件。 Any async calls going on with login pages? 登录页面上正在进行任何异步调用吗?

Success! 成功! Still need to narrow down exactly what change resulted in the problem going away, but the client reports that he no longer has problems logging in. 仍然需要精确地缩小导致问题消失的更改,但是客户端报告他不再有登录问题。

The biggest change that immediately comes to mind was removing session_write_close() just about everywhere. 立即想到的最大变化是几乎在所有地方都删除了session_write_close() It may have been placed AFTER the header redirect in parts of the code, or just having it present may have been the cause. 它可能已放置在标头重定向的部分代码之后,或者可能只是原因而已。 I will experiment with placing it before the redirect. 我将尝试将其放置在重定向之前。

Thanks to all for your suggestions 谢谢大家的建议

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM