问题
您的password_verify 总是返回true,因为通过使用password_verify($_POST["password"], $hash),您实际上是在将$_POST["password"] 与它自己进行比较——因为$hash = $password 和$password = $_POST["password"]。
解决方案
password_verify 应该首先将发布的密码 ($_POST["password"]) 与必须从数据库中获取的密码哈希进行比较。所以你的登录代码应该是这样的:
<?php
include_once "dbconnection.php";
if (isset($_POST["submit"])) {
$username = $_POST["username"];
$password = $_POST["password"];
$sql = "SELECT password FROM users where username = '" . $username . "' LIMIT 1";
$statement = $conn->query($sql);
$credentials = $statement->fetch(PDO::FETCH_ASSOC);
if ($credentials) { // Record found.
$hash = $credentials['password'];
// Compare the posted password with the password hash fetched from db.
if (password_verify($password, $hash)) {
echo "Welcome";
} else {
echo "Wrong Password";
}
} else {
echo 'No credentials found for the given user.';
}
}
请注意,此解决方案对sql injections 开放。请改用准备好的语句(请参阅下面的扩展示例)。
另一个扩展示例:
这是使用password_hash - password_verify 串联的另一个示例。虽然我同意@tadman:你应该使用外部库——作为你希望的框架的一部分提供,或者作为独立组件提供——提供一个可靠的安全层。
请注意,避免所谓的 sql 注入,更具体地说,确保安全的身份验证/授权系统的第一个重要步骤是使用prepared statements。当您使用数据访问层时,请尝试始终应用此准备步骤。
register.php
<?php
require 'connection.php';
// Signalize if a new account could be created, or not.
$accountCreated = FALSE;
/*
* ================================
* Operations upon form submission.
* ================================
*/
if (isset($_POST['submit'])) {
/*
* =======================
* Read the posted values.
* =======================
*/
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
/*
* ===========================
* Validate the posted values.
* ===========================
*/
// Validate the username.
if (empty($username)) {
$errors[] = 'Please provide a username.';
} /* Other validations here using elseif statements */
// Validate the password.
if (empty($password)) {
$errors[] = 'Please provide a password.';
} /* Other validations here using elseif statements */
/*
* ==================================
* Check if user exists. Save if not.
* ==================================
*/
if (!isset($errors)) {
/*
* =============================
* Check if user already exists.
* =============================
*/
/*
* The SQL statement to be prepared. Notice the so-called named markers.
* They will be replaced later with the corresponding values from the
* bindings array when using PDOStatement::bindValue.
*
* When using named markers, the bindings array will be an associative
* array, with the key names corresponding to the named markers from
* the sql statement.
*
* You can also use question mark markers. In this case, the bindings
* array will be an indexed array, with keys beginning from 1 (not 0).
* Each array key corresponds to the position of the marker in the sql
* statement.
*
* @link http://php.net/manual/en/mysqli.prepare.php
*/
$sql = 'SELECT COUNT(*)
FROM users
WHERE username = :username';
/*
* The bindings array, mapping the named markers from the sql
* statement to the corresponding values. It will be directly
* passed as argument to the PDOStatement::execute method.
*
* @link http://php.net/manual/en/pdostatement.execute.php
*/
$bindings = [
':username' => $username,
];
/*
* Prepare the sql statement for execution and return a statement object.
*
* @link http://php.net/manual/en/pdo.prepare.php
*/
$statement = $connection->prepare($sql);
/*
* Execute the prepared statement. Because the bindings array
* is directly passed as argument, there is no need to use any
* binding method for each sql statement's marker (like
* PDOStatement::bindParam or PDOStatement::bindValue).
*
* @link http://php.net/manual/en/pdostatement.execute.php
*/
$statement->execute($bindings);
/*
* Fetch the data and save it into a variable.
*
* @link https://secure.php.net/manual/en/pdostatement.fetchcolumn.php
*/
$numberOfFoundUsers = $statement->fetchColumn(0);
if ($numberOfFoundUsers > 0) {
$errors[] = 'The given username already exists. Please choose another one.';
} else {
/*
* ========================
* Save a new user account.
* ========================
*/
// Create a password hash.
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
$sql = 'INSERT INTO users (
username,
password
) VALUES (
:username,
:password
)';
$bindings = [
':username' => $username,
':password' => $passwordHash,
];
$statement = $connection->prepare($sql);
$statement->execute($bindings);
// Signalize that a new account was successfully created.
$accountCreated = TRUE;
// Reset all values so that they are not shown in the form anymore.
$username = $password = NULL;
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
<meta charset="UTF-8" />
<!-- The above 3 meta tags must come first in the head -->
<title>Demo - Register</title>
<style type="text/css">
.messages { margin-bottom: 10px; }
.messages a { text-transform: uppercase; font-weight: 700;}
.error, .success { margin-bottom: 5px; }
.error { color: #ff0000; }
.success { color: #32cd32; }
.form-group { margin-bottom: 10px; }
.form-group label { display: inline-block; min-width: 90px; }
</style>
</head>
<body>
<h3>
Register
</h3>
<div class="messages">
<?php
if (isset($errors)) {
foreach ($errors as $error) {
?>
<div class="error">
<?php echo $error; ?>
</div>
<?php
}
} elseif ($accountCreated) {
?>
<div class="success">
You have successfully created your account.
<br/>Would you like to <a href="login.php">login</a> now?
</div>
<?php
}
?>
</div>
<form action="" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="<?php echo $username ?? ''; ?>" placeholder="Username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" value="<?php echo $password ?? ''; ?>" placeholder="Password">
</div>
<button type="submit" id="registerButton" name="submit" value="register">
Register
</button>
</form>
</body>
</html>
login.php
<?php
require 'connection.php';
/*
* ================================
* Operations upon form submission.
* ================================
*/
if (isset($_POST['submit'])) {
/*
* =======================
* Read the posted values.
* =======================
*/
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
/*
* ===========================
* Validate the posted values.
* ===========================
*/
// Validate the username.
if (empty($username)) {
$errors[] = 'Please provide a username.';
} /* Other validations here using elseif statements */
// Validate the password.
if (empty($password)) {
$errors[] = 'Please provide a password.';
} /* Other validations here using elseif statements */
/*
* ======================
* Check the credentials.
* ======================
*/
if (!isset($errors)) { // No errors yet.
$sql = 'SELECT username, password
FROM users
WHERE username = :username
LIMIT 1';
$statement = $connection->prepare($sql);
$statement->execute([
':username' => $username,
]);
/*
* Fetch the credentials into an associative array.
* If no record is found, the operation returns FALSE.
*/
$credentials = $statement->fetch(PDO::FETCH_ASSOC);
if ($credentials) { // Record found.
$fetchedUsername = $credentials['username'];
$fetchedPasswordHash = $credentials['password'];
/*
* Compare the posted username with the one saved in db and the posted
* password with the password hash saved in db using password_hash.
*
* @link https://secure.php.net/manual/en/function.password-verify.php
* @link https://secure.php.net/manual/en/function.password-hash.php
*/
if (
$username === $fetchedUsername &&
password_verify($password, $fetchedPasswordHash)
) {
header('Location: welcome.html');
exit();
} else {
$errors[] = 'Invalid credentials. Please try again.';
}
} else {
$errors[] = 'No credentials found for the given user.';
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
<meta charset="UTF-8" />
<!-- The above 3 meta tags must come first in the head -->
<title>Demo - Login</title>
<script type="text/javascript">
function validateForm() {
// ...Some form validation, if needed...
return true;
}
</script>
<style type="text/css">
.messages { margin-bottom: 10px; }
.error { margin-bottom: 5px; color: #ff0000; }
.form-group { margin-bottom: 10px; }
.form-group label { display: inline-block; min-width: 90px; }
</style>
</head>
<body>
<h3>
Login
</h3>
<div class="messages">
<?php
if (isset($errors)) {
foreach ($errors as $error) {
?>
<div class="error">
<?php echo $error; ?>
</div>
<?php
}
}
?>
</div>
<form action="" method="post" onsubmit="return validateForm();">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" value="<?php echo $username ?? ''; ?>" placeholder="Username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" value="<?php echo $password ?? ''; ?>" placeholder="Password">
</div>
<button type="submit" id="loginButton" name="submit" value="login">
Login
</button>
</form>
</body>
</html>
connection.php
<?php
/*
* This page contains the code for creating a PDO connection instance.
*/
// Db configs.
define('HOST', 'localhost');
define('PORT', 3306);
define('DATABASE', 'tests');
define('USERNAME', 'root');
define('PASSWORD', 'root');
define('CHARSET', 'utf8');
$connection = new PDO(
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s', HOST, PORT, DATABASE, CHARSET)
, USERNAME
, PASSWORD
, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => FALSE,
PDO::ATTR_PERSISTENT => FALSE,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
welcome.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes" />
<meta charset="UTF-8" />
<!-- The above 3 meta tags must come first in the head -->
<title>Demo - Welcome</title>
</head>
<body>
<h3>
Welcome
</h3>
<div>
Hi. You are now logged-in.
</div>
</body>
</html>
表定义
注意 password 列的 255 个字符的长度。该字段应该(至少)这么长。更多详情请访问password_hash(在“描述”中)。
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;