问题
I want to make an login system with password verify. I have encrypted my password with password default. I also stored the hash in the database. Now I want to use password verify to make an login system, but the function always reteruns a value of true. can someone explains why? And can someone explain me how I can use password verify with $_POST?
code for the hash
<?php
/**
* Created by PhpStorm.
* User: jbosma
* Date: 24/07/2018
* Time: 23:21
*/
include_once "dbconnection.php";
if (isset($_POST["submit"])) { // send the input
$username = $_POST["username"]; // post the input username
$password = $_POST["password"]; // post the input password
$conn = new PDO("mysql:host=localhost;dbname=loginapp", $user, $pass); // database connection
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // error mode
$hash = PASSWORD_HASH($password, PASSWORD_DEFAULT); // make $hash so you can give that to your password in the query
$sql = "INSERT INTO users (username, password) VALUES ('".$username."','".$hash."')"; // insert the username and the password that will be hashed
$conn->exec($sql); // excecute the query above
header('location: register.php'); // after you submit it redirects to login.php
}
code for password verify
<?php
/**
* Created by PhpStorm.
* User: jbosma
* Date: 27/07/2018
* Time: 12:11
*/
include_once "dbconnection.php";
if (isset($_POST["submit"])) { // send the input
$username = $_POST["username"]; // post the input username
$password = $_POST["password"]; // post the input password
$hash = $password; // hash from password
if (password_verify($_POST["password"], $hash)) {
echo "Welcome";
} else {
echo "Wrong Password";
}
}
?>
回答1:
Have something like this in your reg form, it's using BCRYPT in this instance. This is just showing the password_hash function
Register
// Other code leading up to this..
....
//
$passHash = password_hash($pass, PASSWORD_BCRYPT, array("cost" => 12));
$insrt = "INSERT INTO users (username, password) VALUES (:username, :password)";
$stmt = $pdo->prepare($insrt);
$stmt->bindValue(':username', $username);
$stmt->bindValue(':password', $passHash);
$result = $stmt->execute();
if($result){
// Do whatever you want
login
// Other code leading up to this..
....
//
$validPassword = password_verify($pass, $user['password']);
if($validPassword){
$_SESSION['user_id'] = $user['username'];
$_SESSION['logged_in'] = time();
// redirects in this case
header( "Location: /wherever.php" );
die();
} else{
die('Wrong password!');
}
This is just showing how to use the functions themselves, apply this principle and you should be good to go
回答2:
The problem
Your password_verify
returns always true
because, by using password_verify($_POST["password"], $hash)
, you are actually comparing $_POST["password"]
with itself - since $hash = $password
and $password = $_POST["password"]
.
The solution
The password_verify
should compare the posted password ($_POST["password"]
) with the password hash, which has to be fetched from the database, first. So your login code should look something like this:
<?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.';
}
}
Note that this solution is opened to sql injections. Use prepared statements instead (see the extended example bellow).
Another, extended example:
Here is another example of using the password_hash - password_verify
tandem. Though I agree with @tadman: you should make use of external libraries - offered as part of a framework of your wish, or as standalone components - providing a solide security layer.
Note that the first important step in avoiding the so-called sql injections and, more specifically, in assuring a secure authentication/authorization system, is the use of prepared statements. Try to always apply this preparation step when you are working with a data access layer.
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>
Table definition
Notice the length of 255 characters of the password
column. The field should be (at least) so long. More details at password_hash (in "Description").
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;
来源:https://stackoverflow.com/questions/51730509/how-can-i-make-the-password-verify-function-work-with-post