这是关于如何使用 PHP 的 PDO 对象构建用户注册和登录表单的初学者教程。就目前而言,网络上有数百个“PHP 登录教程”。不幸的是,他们中的绝大多数使用现在被认为已经过时的不安全的密码散列方法和扩展(我在看着你,mysql_query)。
用户表结构。
通过使用非常基本的表结构,我使事情变得简单。显然,如果需要(电子邮件地址和姓名等),您可以对其进行自定义并将您自己的列添加到此表中。本教程假设我们的登录是基于用户名和密码组合(而不是电子邮件和密码组合)。您可以将以下表结构导入数据库:
CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
`password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
注意:我在用户名列中添加了唯一索引。这是因为用户名必须是唯一的!
连接。
首要任务是使用 PDO 对象连接到 MySQL。有关这方面的更深入的教程,您应该阅读我的教程 “使用 PHP 连接到 MySQL”。出于本教程的目的,我创建了一个名为connect.php的文件,我们将在整个脚本中包含该文件:
<?php
//connect.php
/**
* This script connects to MySQL using the PDO object.
* This can be included in web pages where a database connection is needed.
* Customize these to match your MySQL database connection details.
* This info should be available from within your hosting panel.
*/
//Our MySQL user account.
define('MYSQL_USER', 'root');
//Our MySQL password.
define('MYSQL_PASSWORD', '');
//The server that MySQL is located on.
define('MYSQL_HOST', 'localhost');
//The name of our database.
define('MYSQL_DATABASE', 'test');
/**
* PDO options / configuration details.
* I'm going to set the error mode to "Exceptions".
* I'm also going to turn off emulated prepared statements.
*/
$pdoOptions = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false
);
/**
* Connect to MySQL and instantiate the PDO object.
*/
$pdo = new PDO(
"mysql:host=" . MYSQL_HOST . ";dbname=" . MYSQL_DATABASE, //DSN
MYSQL_USER, //Username
MYSQL_PASSWORD, //Password
$pdoOptions //Options
);
//The PDO object can now be used to query MySQL.
上面的代码将使用 PDO 扩展连接到 MySQL 数据库。您将需要阅读代码中散布的注释并更改 MySQL 连接详细信息以匹配您自己的!
用户注册表。
在用户登录之前,他或她需要使用注册表单注册我们的网站。如果注册成功,我们将在用户表中插入一个新的用户帐户。
<?php
//register.php
/**
* Start the session.
*/
session_start();
/**
* Include our MySQL connection.
*/
require 'connect.php';
//If the POST var "register" exists (our submit button), then we can
//assume that the user has submitted the registration form.
if(isset($_POST['register'])){
//Retrieve the field values from our registration form.
$username = !empty($_POST['username']) ? trim($_POST['username']) : null;
$pass = !empty($_POST['password']) ? trim($_POST['password']) : null;
//TO ADD: Error checking (username characters, password length, etc).
//Basically, you will need to add your own error checking BEFORE
//the prepared statement is built and executed.
//Now, we need to check if the supplied username already exists.
//Construct the SQL statement and prepare it.
$sql = "SELECT COUNT(username) AS num FROM users WHERE username = :username";
$stmt = $pdo->prepare($sql);
//Bind the provided username to our prepared statement.
$stmt->bindValue(':username', $username);
//Execute.
$stmt->execute();
//Fetch the row.
$row = $stmt->fetch(PDO::FETCH_ASSOC);
//If the provided username already exists - display error.
//TO ADD - Your own method of handling this error. For example purposes,
//I'm just going to kill the script completely, as error handling is outside
//the scope of this tutorial.
if($row['num'] > 0){
die('That username already exists!');
}
//Hash the password as we do NOT want to store our passwords in plain text.
$passwordHash = password_hash($pass, PASSWORD_BCRYPT, array("cost" => 12));
//Prepare our INSERT statement.
//Remember: We are inserting a new row into our users table.
$sql = "INSERT INTO users (username, password) VALUES (:username, :password)";
$stmt = $pdo->prepare($sql);
//Bind our variables.
$stmt->bindValue(':username', $username);
$stmt->bindValue(':password', $passwordHash);
//Execute the statement and insert the new account.
$result = $stmt->execute();
//If the signup process is successful.
if($result){
//What you do here is up to you!
echo 'Thank you for registering with our website.';
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Register</title>
</head>
<body>
<h1>Register</h1>
<form action="register.php" method="post">
<label for="username">Username</label>
<input type="text" id="username" name="username"><br>
<label for="password">Password</label>
<input type="text" id="password" name="password"><br>
<input type="submit" name="register" value="Register"></button>
</form>
</body>
</html>
需要注意的几点:
- 我们正在使用一种名为 BCRYPT 的受人尊敬的密码散列算法。其他登录教程错误地推广了 md5 和 sha1 等哈希算法。md5 和 sha1 的问题在于它们“太快了”,这基本上意味着密码破解者可以更快地“破解”它们。
- 您需要在此注册表单中添加您自己的错误检查(用户名长度、允许的字符类型等)。您还需要实现自己的处理用户错误的方法,因为上面的代码在这方面非常基础。有关此主题的进一步帮助,请务必查看我的关于在 PHP 中处理表单错误的教程。
- 如果上面的插入代码对您来说完全陌生,那么您可能应该查看我关于使用 PDO 插入的文章。
用户使用 PHP 登录。
使用 PHP 和 PDO 对象登录网站的基本示例:
<?php
//login.php
/**
* Start the session.
*/
session_start();
/**
* Include our MySQL connection.
*/
require 'connect.php';
//If the POST var "login" exists (our submit button), then we can
//assume that the user has submitted the login form.
if(isset($_POST['login'])){
//Retrieve the field values from our login form.
$username = !empty($_POST['username']) ? trim($_POST['username']) : null;
$passwordAttempt = !empty($_POST['password']) ? trim($_POST['password']) : null;
//Retrieve the user account information for the given username.
$sql = "SELECT id, username, password FROM users WHERE username = :username";
$stmt = $pdo->prepare($sql);
//Bind value.
$stmt->bindValue(':username', $username);
//Execute.
$stmt->execute();
//Fetch row.
$user = $stmt->fetch(PDO::FETCH_ASSOC);
//If $row is FALSE.
if($user === false){
//Could not find a user with that username!
//PS: You might want to handle this error in a more user-friendly manner!
die('Incorrect username / password combination!');
} else{
//User account found. Check to see if the given password matches the
//password hash that we stored in our users table.
//Compare the passwords.
$validPassword = password_verify($passwordAttempt, $user['password']);
//If $validPassword is TRUE, the login has been successful.
if($validPassword){
//Provide the user with a login session.
$_SESSION['user_id'] = $user['id'];
$_SESSION['logged_in'] = time();
//Redirect to our protected page, which we called home.php
header('Location: home.php');
exit;
} else{
//$validPassword was FALSE. Passwords do not match.
die('Incorrect username / password combination!');
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="login.php" method="post">
<label for="username">Username</label>
<input type="text" id="username" name="username"><br>
<label for="password">Password</label>
<input type="text" id="password" name="password"><br>
<input type="submit" name="login" value="Login">
</form>
</body>
</html>
逐步解释上面的代码:
- 我们使用函数 session_start 启动会话。这个函数必须在每一页上调用。
- 我们需要我们的 connect.php 文件,它连接到 MySQL 并实例化 PDO 对象。
- 如果 POST 变量“login”存在,我们假设用户正在尝试登录我们的网站。
- 我们从登录表单中获取字段值。
- 使用我们提供的用户名,我们尝试从 MySQL 表中检索相关用户。我们通过使用准备好的 SELECT 语句来做到这一点。
- 如果存在具有该用户名的用户,我们将使用函数 password_verify 比较两个密码(这会为您处理哈希比较)。
- 如果密码哈希匹配,我们为用户提供登录会话。我们通过创建两个名为“user_id”和“logged_in”的会话变量来做到这一点。
- 然后我们将用户重定向到 home.php,这是我们的登录保护页面。
注意:您需要实现自己的方式来处理用户错误。在本教程中,我使用的是 die 语句,这有点令人讨厌。
受保护的页面。
我们的受保护页面称为 home.php。我保持简单:
<?php
//home.php
/**
* Start the session.
*/
session_start();
/**
* Check if the user is logged in.
*/
if(!isset($_SESSION['user_id']) || !isset($_SESSION['logged_in'])){
//User not logged in. Redirect them back to the login.php page.
header('Location: login.php');
exit;
}
/**
* Print out something that only logged in users can see.
*/
echo 'Congratulations! You are logged in!';
一步步:
- 我使用 session_start 开始会话。这很重要,因为如果没有有效的用户会话,我们的登录系统将无法工作。
- 我们检查用户是否具有所需的会话变量(用户 ID 和登录时间戳)。如果用户没有这些会话变量中的任何一个,我们只需将它们重定向回 login.php 页面。显然,您可以自定义它以满足您自己的需求。
- 我们打印出一条测试消息,只是为了表明登录系统按预期运行。
如果您不熟悉这一切,那么您可能应该下载下面的代码示例并在您的开发/本地机器上实现登录系统。确保您阅读了代码中包含的注释!如果您有任何问题或建议,请务必在下面发表评论!