php轻松登录系统失败

php easy login system fail

更新:

我检查了问题出在哪里。

首先,我在 process_login.php 中添加了回显,然后删除了 header('location...protected_page.php)。登录函数(在 functions.php 中)完成了他的工作,并正确地编写了:$_SESSION['user_id'] 以及 $_SESSION['username']$_SESSION['login_string']

然后我把process_login.php里面的echo删掉,把header('location..改成protected_page.php.(我回到原来的代码),然后加了一些在任何 if function login_check($mysqli) { echo "<br>userid:".$_SESSION['user_id']; echo "<br>username:".$_SESSION['username']; echo "<br>loginstring:".$_SESSION['login_string'];

之前回显 login_check

结果为空,所以SESSION变量在函数登录中正确写入,但在header到protected_page之后变量似乎被删除了。 可能 sec_session_start() 函数运行不正常?无论如何,我会听从@Manikiran 的建议,我会继续工作,删除加密或其他东西......谢谢@Manikiran 和@lps

原始问题:

我正在尝试做一个基本的登录系统。但是,我所做的不起作用。该系统基本上是一个 index.php 放置您的凭据,然后 php 验证您的用户是否存在以及密码是否正确。然后用户被重定向到 protected_page.php。好的,身份验证部分工作正常,当用户和密码正确时,网页会将您重定向到 protected_page.php,当用户和密码不正确时,页面会告诉您。

现在,问题: 在 protected_page.php 中显然有一个 session "checker",我的意思是如果你与你的用户正确连接和密码,该页面将显示您的私人 html 信息,如果您未连接,该页面将不会显示任何内容。这是行不通的,尽管您与用户正确连接,但该页面告诉您您尚未登录.... (注意:index.php 还包含受保护的 html,两者均不起作用)

代码: (注意:sec_session_starts() 应该是一种防止 session 劫持的方法,它当然包含 session_start())

index.php: (我想它工作正常,因为它必须重定向到 protected_page() . 也许问题出在我设置 session 数据部分的 login(...) 函数...).

 <?php
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';

sec_session_start();

if (login_check($mysqli) == true) {
    $logged = 'in';
} else {
    $logged = 'out';
}
?>
<!DOCTYPE html>
<html>
    <head>
        <title>Secure Login: Log In</title>

        <script type="text/JavaScript" src="js/sha512.js"></script> 
        <script type="text/JavaScript" src="js/forms.js"></script> 
    </head>
    <body>
        <?php
        if (isset($_GET['error'])) {
            echo '<p class="error">Error Logging In!</p>';
        }
        ?> 
        <form action="includes/process_login.php" method="post" name="login_form">                      
            Email: <input type="text" name="email" />
            Password: <input type="password" 
                             name="password" 
                             id="password"/>
            <input type="button" 
                   value="Login" 
                   onclick="formhash(this.form, this.form.password);" /> 
        </form>

<?php
        if (login_check($mysqli) == true) {
                        echo '<p>Currently logged ' . $logged . ' as ' . htmlentities($_SESSION['username']) . '.</p>';

            echo '<p>Do you want to change user? <a href="includes/logout.php">Log out</a>.</p>';
        } else {
                        echo '<p>Currently logged ' . $logged . '.</p>';
                        echo "<p>If you don't have a login, please <a href='register.php'>register</a></p>";
                }
?>      
    </body>
</html>

process_login.php (我猜工作正常)

<?php
include_once 'db_connect.php';
include_once 'functions.php';

sec_session_start(); // Our custom secure way of starting a PHP session.

if (isset($_POST['email'], $_POST['p'])) {
    $email = $_POST['email'];
    $password = $_POST['p']; // The hashed password.

    if (login($email, $password, $mysqli) == true) {
        // Login success 
        header('Location: ../protected_page.php');
    } else {
        // Login failed 
        header('Location: ../index.php?error=1');
    }
} else {
    // The correct POST variables were not sent to this page. 
    echo 'Invalid Request';
}

functions.php:

<?php
include_once 'psl-config.php';
require_once 'passwordLib.php';


function sec_session_start() {
    $session_name = 'sec_session_id';   // Set a custom session name
    $secure = true;
    // This stops JavaScript being able to access the session id.
    $httponly = true;
    // Forces sessions to only use cookies.
    if (ini_set('session.use_only_cookies', 1) === FALSE) {
        header("Location: ../error.php?err=Could not initiate a safe session (ini_set)");
        exit();
    }
    // Gets current cookies params.
    $cookieParams = session_get_cookie_params();
    session_set_cookie_params($cookieParams["lifetime"],
        $cookieParams["path"], 
        $cookieParams["domain"], 
        $secure,
        $httponly);
    // Sets the session name to the one set above.
    session_name($session_name);
    session_start();            // Start the PHP session 
    session_regenerate_id(true);    // regenerated the session, delete the old one. 
}


function login($email, $password, $mysqli) {
    // Using prepared statements means that SQL injection is not possible. 
    if ($stmt = $mysqli->prepare("SELECT id, username, password 
        FROM members
       WHERE email = ?
        LIMIT 1")) {
        $stmt->bind_param('s', $email);  // Bind "$email" to parameter.
        $stmt->execute();    // Execute the prepared query.
        $stmt->store_result();

        // get variables from result.
        $stmt->bind_result($user_id, $username, $db_password);
        $stmt->fetch();

        if ($stmt->num_rows == 1) {
            // If the user exists we check if the account is locked
            // from too many login attempts 

            if (checkbrute($user_id, $mysqli) == true) {
                // Account is locked 
                // Send an email to user saying their account is locked
                return false;
            } else {
                // Check if the password in the database matches
                // the password the user submitted. We are using
                // the password_verify function to avoid timing attacks.
                if (password_verify($password, $db_password)) {
                    // Password is correct!
                    // Get the user-agent string of the user.
                    $user_browser = $_SERVER['HTTP_USER_AGENT'];
                    // XSS protection as we might print this value
                    $user_id = preg_replace("/[^0-9]+/", "", $user_id);
                    $_SESSION['user_id'] = $user_id;
                    // XSS protection as we might print this value
                    $username = preg_replace("/[^a-zA-Z0-9_\-]+/", 
                                                                "", 
                                                                $username);
                    $_SESSION['username'] = $username;
                    $_SESSION['login_string'] = hash('sha512', 
                              $db_password . $user_browser);
                    // Login successful.
                    return true;
                } else {
                    // Password is not correct
                    // We record this attempt in the database
                    $now = time();
                    $mysqli->query("INSERT INTO login_attempts(user_id, time)
                                    VALUES ('$user_id', '$now')");
                    return false;
                }
            }
        } else {
            // No user exists.
            return false;
        }
    }
}

function checkbrute($user_id, $mysqli) {
    // Get timestamp of current time 
    $now = time();

    // All login attempts are counted from the past 2 hours. 
    $valid_attempts = $now - (2 * 60 * 60);

    if ($stmt = $mysqli->prepare("SELECT time 
                             FROM login_attempts 
                             WHERE user_id = ? 
                            AND time > '$valid_attempts'")) {
        $stmt->bind_param('i', $user_id);

        // Execute the prepared query. 
        $stmt->execute();
        $stmt->store_result();

        // If there have been more than 5 failed logins 
        if ($stmt->num_rows > 5) {
            return true;
        } else {
            return false;
        }
    }
}

function login_check($mysqli) {
    // Check if all session variables are set 
    if (isset($_SESSION['user_id'], 
                        $_SESSION['username'], 
                        $_SESSION['login_string'])) {

        $user_id = $_SESSION['user_id'];
        $login_string = $_SESSION['login_string'];
        $username = $_SESSION['username'];

        // Get the user-agent string of the user.
        $user_browser = $_SERVER['HTTP_USER_AGENT'];

        if ($stmt = $mysqli->prepare("SELECT password 
                                      FROM members 
                                      WHERE id = ? LIMIT 1")) {
            // Bind "$user_id" to parameter. 
            $stmt->bind_param('i', $user_id);
            $stmt->execute();   // Execute the prepared query.
            $stmt->store_result();

            if ($stmt->num_rows == 1) {
                // If the user exists get variables from result.
                $stmt->bind_result($password);
                $stmt->fetch();
                $login_check = hash('sha512', $password . $user_browser);

                if (hash_equals($login_check, $login_string) ){
                    // Logged In!!!! 
                    return true;
                } else {
                    // Not logged in 
                    return false;
                }
            } else {
                // Not logged in 
                return false;
            }
        } else {
            // Not logged in 
            return false;
        }
    } else {
        // Not logged in 
        return false;
    }
}

function esc_url($url) {

    if ('' == $url) {
        return $url;
    }

    $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\x80-\xff]|i', '', $url);

    $strip = array('%0d', '%0a', '%0D', '%0A');
    $url = (string) $url;

    $count = 1;
    while ($count) {
        $url = str_replace($strip, '', $url, $count);
    }

    $url = str_replace(';//', '://', $url);

    $url = htmlentities($url);

    $url = str_replace('&amp;', '&#038;', $url);
    $url = str_replace("'", '&#039;', $url);

    if ($url[0] !== '/') {
        // We're only interested in relative links from $_SERVER['PHP_SELF']
        return '';
    } else {
        return $url;
    }
}

protected_page.php:(或者问题可能出在 login_check 那里检查 session 数据

<?php
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';

sec_session_start();

?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Secure Login: Protected Page</title>
        <link rel="stylesheet" href="styles/main.css" />
    </head>
    <body>

        <?php if (login_check($mysqli) == true) : ?>
            <p>Welcome <?php echo htmlentities($_SESSION['username']); ?>!</p>
            <p>
                This is an example protected page.  To access this page, users
                must be logged in.  At some stage, we'll also check the role of
                the user, so pages will be able to determine the type of user
                authorised to access the page.
            </p>
            <p>Return to <a href="index.php">login page</a></p>
        <?php else : ?>
            <p>
                <span class="error">You are not authorized to access this page.</span> Please <a href="index.php">login</a>.
            </p>
        <?php endif; ?>
    </body>
</html>

psl-config 和 db_connect 与数据库连接,我认为它工作正常。 passwordLib.php 是一个库,因为我的服务器有 php 5.4 而我正在使用 php 5 函数。这个库应该有助于一些密码加密或其他什么,我认为问题不在这里。无论如何我把代码:

<?php
/**
 * PHP 5.5-like password hashing functions
 *
 * Provides a password_hash() and password_verify() function as appeared in PHP 5.5.0
 * 
 * See: http://php.net/password_hash and http://php.net/password_verify
 * 
 * @link https://github.com/Antnee/phpPasswordHashingLib
 */

require_once('passwordLibClass.php');

if (!function_exists('password_hash')){
    function password_hash($password, $algo=PASSWORD_DEFAULT, $options=array()){
        $crypt = NEW Antnee\PhpPasswordLib\PhpPasswordLib;
        $crypt->setAlgorithm($algo);

        $debug  = isset($options['debug'])
                ? $options['debug']
                : NULL;

        $password = $crypt->generateCryptPassword($password, $options, $debug);

        return $password;
    }
}

if (!function_exists('password_verify')){
    function password_verify($password, $hash){
        return (crypt($password, $hash) === $hash);
    }
}

if (!function_exists('password_needs_rehash')){
    function password_needs_rehash($hash, $algo, $options=array()){
        $crypt = NEW Antnee\PhpPasswordLib\PhpPasswordLib;
        return !$crypt->verifyCryptSetting($hash, $algo, $options);
    }
}

if (!function_exists('password_get_info')){
    function password_get_info($hash){
        $crypt = NEW Antnee\PhpPasswordLib\PhpPasswordLib;
        return $crypt->getInfo($hash);
    }
}

passwordlibclass.php:

<?php
/**
 * PHP 5.5-like password hashing functions
 *
 * Provides a password_hash() and password_verify() function as appeared in PHP 5.5.0
 * 
 * See: http://php.net/password_hash and http://php.net/password_verify
 * 
 * @link https://github.com/Antnee/phpPasswordHashingLib
 */


namespace Antnee\PhpPasswordLib;

if (!defined('PASSWORD_BCRYPT')) define('PASSWORD_BCRYPT', 1);

// Note that SHA hashes are not implemented in password_hash() or password_verify() in PHP 5.5
// and are not recommended for use. Recommend only the default BCrypt option
if (!defined('PASSWORD_SHA256')) define('PASSWORD_SHA256', -1);
if (!defined('PASSWORD_SHA512')) define('PASSWORD_SHA512', -2);

if (!defined('PASSWORD_DEFAULT')) define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);

class PhpPasswordLib{

    CONST BLOWFISH_CHAR_RANGE = './0123456789ABCDEFGHIJKLMONPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    CONST BLOWFISH_CRYPT_SETTING = 'a$'; 
    CONST BLOWFISH_CRYPT_SETTING_ALT = 'y$'; // Available from PHP 5.3.7
    CONST BLOWFISH_ROUNDS = 10;
    CONST BLOWFISH_NAME = 'bcrypt';

    // Note that SHA hashes are not implemented in password_hash() or password_verify() in PHP 5.5
    // and are not recommended for use. Recommend only the default BCrypt option
    CONST SHA256_CHAR_RANGE = './0123456789ABCDEFGHIJKLMONPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    CONST SHA256_CRYPT_SETTING = '$';
    CONST SHA256_ROUNDS = 5000;
    CONST SHA256_NAME = 'sha256';

    CONST SHA512_CHAR_RANGE = './0123456789ABCDEFGHIJKLMONPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    CONST SHA512_CRYPT_SETTING = '$';
    CONST SHA512_ROUNDS = 5000;
    CONST SHA512_NAME = 'sha512';


    /**
     * Default Crypt Algorithm
     * 
     * @var INT
     */
    private $algorithm = PASSWORD_BCRYPT;


    /**
     * Name of the current algorithm
     *
     * @var STRING
     */
    private $algoName;


    /**
     * Setting for PHP Crypt function, defines algorithm
     * 
     * Default setting is 'a$' : BCrypt
     * 
     * @var STRING
     */
    protected $cryptSetting;


    /**
     * Setting for PHP Crypt function, defines processing cost
     * 
     * Default setting is '08$' for BCrypt rounds
     * 
     * @var INT
     */
    protected $rounds;


    /**
     * Salt Character Count for Crypt Functions
     * 
     * @var INT
     */
    protected $addSaltChars;


    /**
     * Salt Character Range for Crypt Functions
     * 
     * @var STRING 
     */
    protected $saltCharRange;


    /**
     * Class Constructor
     */
    public function __construct(){
        // Initialise default algorithm
        $this->setAlgorithm($this->algorithm);
    }


    /**
     * Generate Crypt Password
     * 
     * @param STRING $password The password to encode
     * @param ARRAY $options Cost value, and Salt if required
     * @param BOOL $debug If true will return time to calculate hash
     * @return STRING The encoded password
     */
    public function generateCryptPassword($password, $options = array(), $debug = FALSE){
        $startTime  = microtime(TRUE);
        if (isset($options['cost'])) $this->setCost($options['cost']);
        $salt       = $this->cryptSalt(@$options['salt']);
        $crypt      = crypt($password, $salt);
        $endTime    = microtime(TRUE);
        if ($debug){
            $calcTime = $endTime - $startTime;
            return $calcTime;
        }
        return $crypt;
    }


    /**
     * Generate Crypt Salt
     * 
     * Generates a salt suitable for Crypt using the defined crypt settings
     * 
     * @param STRING $salt Override random salt with predefined value
     * @return STRING
     */
    public function cryptSalt($salt=NULL){
        if (empty($salt)){
            for ($i = 0; $i<$this->addSaltChars; $i++){
                $salt .= $this->saltCharRange[rand(0,(strlen($this->saltCharRange)-1))];
            }
        }
        $salt = $this->cryptSetting.$this->rounds.$salt.'$';
        return $salt;
    }


    /**
     * Set Crypt Setting
     * 
     * @param type $setting
     * @return \Antnee\PhpPasswordLib\PhpPasswordLib
     */
    public function cryptSetting($setting){
        $this->cryptSetting = $setting;
        return $this;
    }




       /**
         * Salt Character Count
         * 
         * @param INT $count Number of characters to set
         * @return \Antnee\PhpPasswordLib\PhpPasswordLib|boolean
         */
        public function addSaltChars($count){
            if (is_int($count)){
                $this->addSaltChars = $count;
                return $this;
            } else {
                return FALSE;
            }
        }


        /**
         * Salt Character Range
         * 
         * @param STRING $chars
         * @return \Antnee\PhpPasswordLib\PhpPasswordLib|boolean
         */
        public function saltCharRange($chars){
            if (is_string($chars)){
                $this->saltCharRange = $chars;
                return $this;
            } else {
                return FALSE;
            }
        }


        /**
         * Set Crypt Algorithm
         * 
         * @param INT $algo
         * @return \Antnee\PhpPasswordLib\PhpPasswordLib
         */
        public function setAlgorithm($algo=NULL){
            switch ($algo){
                case PASSWORD_SHA256:
                    $this->algorithm = PASSWORD_SHA256;
                    $this->cryptSetting(self::SHA256_CRYPT_SETTING);
                    $this->setCost(self::SHA256_ROUNDS);
                    $this->addSaltChars(16);
                    $this->saltCharRange(self::SHA256_CHAR_RANGE);
                    $this->algoName = self::SHA256_NAME;
                    break;
                case PASSWORD_SHA512:
                    $this->algorithm = PASSWORD_SHA512;
                    $this->cryptSetting(self::SHA512_CRYPT_SETTING);
                    $this->setCost(self::SHA512_ROUNDS);
                    $this->addSaltChars(16);
                    $this->saltCharRange(self::SHA512_CHAR_RANGE);
                    $this->algoName = self::SHA512_NAME;
                    break;
                case PASSWORD_BCRYPT:
                default:
                    $this->algorithm = PASSWORD_BCRYPT;
                    if (version_compare(PHP_VERSION, '5.3.7') >= 1){
                        // Use improved Blowfish algorithm if supported
                        $this->cryptSetting(self::BLOWFISH_CRYPT_SETTING_ALT);
                    } else {
                        $this->cryptSetting(self::BLOWFISH_CRYPT_SETTING);
                    }
                    $this->setCost(self::BLOWFISH_ROUNDS);
                    $this->addSaltChars(22);
                    $this->saltCharRange(self::BLOWFISH_CHAR_RANGE);
                    $this->algoName = self::BLOWFISH_NAME;
                    break;
            }
            return $this;
        }


        /**
         * Set Cost
         * 
         * @todo implement
         * 
         * @return \Antnee\PhpPasswordLib\PhpPasswordLib
         */
        public function setCost($rounds){
            switch ($this->algorithm){
                case PASSWORD_BCRYPT:
                    $this->rounds = $this->setBlowfishCost($rounds);
                    break;
                case PASSWORD_SHA256:
                case PASSWORD_SHA512:
                    $this->rounds = $this->setShaCost($rounds);
                    break;
            }
            return $this;
        }


        /**
         * Set Blowfish hash cost
         * 
         * Minimum 4, maximum 31. Value is base-2 log of actual number of rounds, so
         * 4 = 16, 8 = 256, 16 = 65,536 and 31 = 2,147,483,648
         * Defaults to 8 if value is out of range or incorrect type
         * 
         * @param int $rounds
         * @return STRING
         */
        private function setBlowfishCost($rounds){
            if (!is_int($rounds) || $rounds < 4 || $rounds > 31){
                $rounds = $rounds = self::BLOWFISH_ROUNDS;
            }
            return sprintf("%02d", $rounds)."$";
        }


        /**
         * Set SHA hash cost
         * 
         * Minimum 1000, maximum 999,999,999
         * Defaults to 5000 if value is out of range or incorrect type
         * 
         * @param INT $rounds
         * @return STRING
         */
        private function setShaCost($rounds){
            if (!is_int($rounds) || $rounds < 1000 || $rounds > 999999999){
                switch ($this->algorithm){
                    case PASSWORD_SHA256:
                        $rounds = self::SHA256_ROUNDS;
                    case PASSWORD_SHA512:
                    default:
                        $rounds = self::SHA512_ROUNDS;
                }
            }
            return "rounds=" . $rounds ."$";
        }


        /**
         * Get hash info
         *
         * @param STRING $hash
         * @return ARRAY
         */
        public function getInfo($hash){
            $params = explode("$", $hash);
            if (count($params) < 4) return FALSE;

            switch ($params['1']){
                case '2a':
                case '2y':
                case '2x':
                    $algo = PASSWORD_BCRYPT;
                    $algoName = self::BLOWFISH_NAME;
                    break;
                case '5':
                    $algo = PASSWORD_SHA256;
                    $algoName = self::SHA256_NAME;
                    break;
                case '6':
                    $algo = PASSWORD_SHA512;
                    $algoName = self::SHA512_NAME;
                    break;
                default:
                    return FALSE;
            }

            $cost = preg_replace("/[^0-9,.]/", "", $params['2']);

            return array(
                'algo' => $algo,
                'algoName' => $algoName,
                'options' => array(
                    'cost' => $cost
                ),
            );
        }


        /**
         * Verify Crypt Setting
         * 
         * Checks that the hash provided is encrypted at the current settings or not,
         * returning BOOL accordingly
         * 
         * @param STRING $hash
         * @return BOOL
         */
        public function verifyCryptSetting($hash, $algo, $options=array()){
            $this->setAlgorithm($algo);
            if (isset($options['cost'])) $this->setCost($options['cost']);

            $setting = $this->cryptSetting.$this->rounds;

            return (substr($hash, 0, strlen($setting)) === $setting);
        }
    }

最后评论:我保证我已经搜索了很多但在其他问题中没有发现这个特定问题。谢谢。

问题是 hash_equals() 无法正常工作,尽管比较的两个变量完全相同,但函数 returns 为假。我试图将其更改为不同的库,但没有办法。 (我记得我的主机有 php 5.4,我不能使用 password_hash() 函数。

唯一的解决方案是更改它或找到一个可以正确替换 password_hash() 的库。