PHP 作为一种动态Web设计语言,就少不了与用户交互的功能。最基本、最普遍的交互功能之一,就是注册登录的功能。今天就来讲讲怎么用 PHP 写登录 注册页面吧。
最基本的功能实现,其实只用 PHP 就 OK ,然而对于一个真正的网站而言,仅仅用 PHP 写显然是不现实的……所以,今天要说的,其实是用 PHP + MySQL 来完成整个登录注册系统。
整个系统的构建其实很简单,大概分为两个部分、五步。PHP 部分,登录前端、登录后端、注册前端、注册后端;MySQL 部分:用户表单。下面就一步步来看咯。
至于服务器怎么搭建 PHP+MySQL 环境我就不多说了,选择也很多(LNMP、LAMP、LNMPA 等等),接下来直接进行创建数据库的操作。数据库管理系统是必不可少的,因为这将大大减轻整个数据库的维护负担,让我们能够从冗长繁杂的 SQL 语句中解脱出来,并且让数据库的结构、数据更加直观。PHP 下的数据库管理系统,当然是选择 phpMyAdmin 咯。
如果你使用了 LNMP 一键包或类似工具创建了一个虚拟主机,并在创建时已经建立了对应的数据库,可以跳过此步骤。
首先登录,在左侧就可以看到已经建立的数据库了:
由于我们需要新建一个网站,自然也就需要新建一个数据库。单击树形图中的“新建”链接,右侧会跳出已建立的数据库列表,以及给出了一个新建数据库的输入框。
“数据库名”可以随便写(不能中文),建议起一个好记且与网站相关的名称,“排序规则”无需修改,默认即可。点击创建,数据库就创建好了。
可以看到新建了一个空数据库,里面并没有表。
表的创建也很简单,在创建好的数据库的管理界面就直接提供了“新建数据表”的选项,输入名字及字段数即可。在一个注册/登录系统中,必不可少的两个字段是用户名和密码,不过这里强烈建议添加 “UID” 字段,使数据表具有索引,这样才能对表中的每一行进行便捷、高效的管理。
于是我们要建立的数据表参数设置如下:
设置完毕后,点击“执行”即可创建一个新表。
执行结束之后,会自动跳转到如下的字段设置页面,“UID” 根据预计用户数目,选择相应的 INT 类型,这里我选择了 “TINYINT” 类型,即 -128~127 的符号数或者 0~255 的无符号数范围。需要更多的用户数量,选更大的数据类型就是咯。“username” 和 “password” 字段由于需要存储字符串,所以选择 “TEXT” 类型。
对于 UID 字段,务必在后面的 “A_I”(AUTO_INCREMENT 的缩写)复选框处打勾,以启用字段的自增功能;打勾后会弹出如下的“添加索引”对话框,可以保留默认直接单击“执行”即可。
全部设置好之后,单击字段设置页的“保存”,即可完成表的创建。
创建好之后的表结构如下图所示。
这里的编写实际上分前后端,前端对应用户界面,后端对应数据处理。我们先写前端,再写后端,这样可以更方便的处理变量的传递。为了一些后续功能的实现,我就把前后端放在一个 PHP 文件中了。
为什么先写登录?因为好写啊……
直接上代码,必要的地方会给注释。
<!doctype html>
<!-- HTML 头部,用于声明网页的类型和编码等,并设置网页标题 -->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录</title>
</head>
<!-- 创建一个表单,用于将数据以 POST 方式提交给后端处理 -->
<!-- "action="后面跟的是PHP语句,用于将表单中的 Sumbit 类型按钮所提交的内容传给本页面 -->
<form id="form1" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']);>" method="post">
<input name="username" type="text" placeholder="输入用户名" />
<input name="password" type="password" placeholder="输入密码" />
<button name="submit" type="submit">登录</button>
</form>
其实很简单,也很短的一段代码,一看就懂。
需要注意的几点:
1.要提交的数据一定要放在 form 标签之下,这样才能够正常的把表单数据 POST 给 PHP ;
2. input 标签的 name 参数是 POST 的变量名,后端需与之对应(后面会写);
3. action 参数中之所以用了 htmlspecialchars() 函数,是为了防攻击,将可能被附加在地址上的代码格式化,使其无法执行。
上面前端用的是 HTML 语言,这里就要用 PHP 语言咯。同样的简短。
<?php
$link=mysqli_connect(" "," "," "," ");//连接到数据库服务器,四个参数依次为主机名、 MySQL 用户名、 MySQL 密码、要使用的数据库名
if($link)//判断数据库是否连接成功
{
if(isset($_POST["submit"]))//判断是否有数据提交
{
mysqli_query($link,'SET NAMES UTF8');
$name=$_POST["username"];
$password=$_POST["password"];//将 POST 得到的用户名和密码赋值给变量(这里引号中的变量参数即为上面 input 标签中的 name 参数)
$stmt=mysqli_prepare($link, "SELECT password FROM users WHERE name=?");
$stmt->bind_param('s', $name);
$stmt->execute();
$stmt->bind_result($pa);
$stmt->fetch();
$stmt->close();//执行数据库查询(具体函数用法请自行百度,篇幅所限不再赘述),之所以用 mysqli_prepare ,是因为它可以将提交的数据完全格式化成对应数据类型的标准形式,是防SQL注入最根本的手段之一
if($pa==$password)//判断密码是否匹配
{
echo '登录成功。';
}
else
{
echo '登录失败。';
}
}
}
?>
这段后端代码插入到前端代码的 body 标签即可正常被调用执行。
学学 PHP MySQLi 函数吧, PHP MySQL 函数由于太容易有注入漏洞,而且语法不够友好,已经被 PHP 官方弃用!
下面说说注册页面怎么写。(其实就是表单内容,以及数据库操作多了一些……)
注册相比于登录,复杂的地方在于后端 PHP 需要对用户输入的内容进行比对,并给出相应的提示,以及大量的数据库操作。
数据库端已经处理完毕,所以直接开始写前后端就可以。
还是老样子,直接上代码,要说明的就标注在注释里咯。
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注册</title>
</head>
<form id="form1" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']);>" method="post">
<input name="username" type="text" placeholder="输入用户名" />
<input name="password" type="password" placeholder="输入密码" />
<input name="password2" type="password" placeholder="确认密码" />
<button id="submit1" name="submit" type="submit">注册</button>
</form>
只需要特别提示一点,两个密码输入框的名称一定要分开,其他的,参考登录就好。
可以看到有多重条件判断嵌套,这也是写注册后端的难点之一了。
第二个难点,其实就是由于需要插入 SQL 表中的数据比较多,所以 SQL 语句不太好构造,尤其是对于 mysqli_prepare 这种变量和语句分离的函数。
注释也没有太多,该有的在登录的代码中都有了,参考一下即可。
<?php
$link=mysqli_connect(" "," "," "," ");//连接到数据库服务器,四个参数依次为主机名、 MySQL 用户名、 MySQL 密码、要使用的数据库名
if ($link)//判断连接是否成功
{
if(isset($_POST["submit"]))//判断是否有数据提交
{
$name=trim($_POST["username"]);// trim 函数用于去除掉不必要的空格
$password1=trim($_POST["password"]);
$password2=trim($_POST["password2"]);
if($password1==$password2)//确认密码是否正确
{
mysqli_query($link,'SET NAMES UTF8');
$stmt1=mysqli_prepare($link, "SELECT count(*) FROM users WHERE name=?");
$stmt1->bind_param('s', $name);
$stmt1->execute();
$stmt1->bind_result($pa);
$stmt1->fetch();
$stmt1->close();
if($pa==1)//判断数据库表中是否已存在该用户名
{
echo '该用户名已被注册。';
}
else
{
mysqli_query($link,'SET NAMES UTF8');
$stmt2=mysqli_prepare($link, "INSERT INTO users (name, password) VALUES(?, ?)");
$stmt2->bind_param('ss', $name, $password2);
$stmt2->execute();
$stmt2->close();//写入注册信息
}
}
else
{
echo '两次输入的密码不一致。';
}
}
}
?>
同样的,这段后端代码插入到前端代码的 body 标签即可正常被调用执行。
我还是要再次嘱咐:
学学 PHP MySQLi 函数吧, PHP MySQL 函数由于太容易有注入漏洞,而且语法不够友好,已经被 PHP 官方弃用!
下面介绍怎么把密码加密,以及加入Email验证的方法。
事实上,在前面的代码中,很容易就会发现一个大问题:密码是以明文保存的。显然这是一种极其不安全,也是极其不负责任的一种做法。所以我们需要在之前的代码中做出一定的修改,让密码不再以明文形式储存。如此一来,用户的密码明文将只有用户知道,这也是符合规范的。
(1)认识 PHP crypt() 函数
学习一个 PHP 函数最好的方法可能就是看看函数的用法详解了。虽然可能有些晦涩难懂,上面的表达也不是非常清晰,不过对于一个函数的工作方式的了解,还是很有用处的。
注意:不要使用标准 DES 加密算法,因其最大加密长度为 8 位,超过 8 位的密码加密结果将完全相同!同时切勿将加密循环次数设置过大,否则会使 PHP 线程过长时间运行,网页无法正常加载。
crypt() 函数的用法相当简单,重点讲一下盐值的问题。
指定不同加密算法的方式是设置相应的盐值前缀。每种加密算法对盐值的要求都不同,请根据具体加密算法要求设置符合要求的盐值( W3School 中非常详细)。盐值可以用变量拼凑,也可指定为固定字符串,但必须确保盐值始终已知,以免后期无法进行密码比对。
PHP 拼凑变量的方法比较简单,将不同变量用“.”分隔即可,就像下面这样:
$salt = "$name.$password";
假设 name 变量的值为 1 , password 变量的值为 2 ,那么 salt 变量在拼凑之后的值就是 12 .
(2)应用加密
应用加密说白了就是把原本保存明文密码的地方设置上加密函数而已。以下代码建立在已经设置好 salt 变量的基础上。盐值是作为 crypt() 函数的一个参数存在的,加进去就好。
$password=crypt(trim($_POST["password"]).$name, $salt);
这里面其实包含了一个小技巧:加密的字符串不是单纯的密码,而是密码和用户名拼凑出的新字符串。这样的好处是,即使加密后的字符串遭到破解,解密结果也不是密码。
其实在盐值里面混入用户名,使每个用户都有独立的盐值,会大大增加安全性——就算被拖库,也还需要挨个破解密码。
至于用密码做盐值加密密码的问题,我不是密码学的,所以并不知道会更安全还是更危险……