上回我们讲到【session+cookie简单讲解以及持久化登录实现】,我们通过在服务器端保存session,客户端保存cookie的方式,跳过繁琐的登录验证,快速验证用户是否登录,以及用户的身份信息。今天来学习另一种思路。
观察上一篇文章中的思路,我们不难看出:
session存在于服务器内存,那么我们就不得不考虑高并发。我们很轻易的想到,高并发请求,将会快速的将服务器的内存占满,坏起来了!
如果我们非常有钱,我们可以购置114台服务器作为一个“集群”,存放与处理session的能力增加了。可是,用户两次身份验证,必须由同一台机器来完成,因为session存在于服务器,他们不共享。
如果我们很有钱,那么可以通过购置1919GB的内存,来缓解这种情况,但是现实往往事与愿违,我们希望找到一种节省空间的办法,来解决用户的登录问题。
说到token就不得不提一下新出的愚者2费token
token又名令牌,顾名思义,是一种身份验证标识。就如同校园卡一般,学校保安凭此确认你是本校学生,并且让你进门。因为校园卡只能由学校签发,而不是其他的机构。校园卡可理解为一种token。
校园卡只能由学校签发,这意味着我们只要给用户一种【只能由服务器下发】的令牌,可以不用查询数据库,就确认用户的信息。绕过数据库查询,能够减少数据库的压力,这是好的。除此之外我们还要能够确认,令牌来自服务器而不是个人伪造的。
综上所述,token需要有两个特性
token分为分发token和验证token,分发token保证token的性质1,即只能由服务器签发,而验证token则维护了token的第二个性质,即服务器能够验证token是否出自自己签发。我们一个一个来看
初次登录的时候,我们将用户的信息(比如用户名),加上一个只有服务器知道的密钥,利用不可逆的加密算法,得到一个token。
因为密钥只有服务器知道,那么就保证了token的第一个特性:只有服务器有能力签发token。
我们将token通过cookie的形式下发给用户,这样每次用户访问的时候,都会带上token,方便服务器验证。除此之外,我们还将用户名存储在cookie中,作为用户的身份信息。注意token和用户身份信息的存放,是两个条目,即
key1 -> 身份信息
key2 -> token
因为token只能由服务器制造,我们利用相同的密钥,再复现一次token的制造,如果两次制造的token相同,那么表示验证成功。这样做保证了token的第二个性质:服务器有能力确实用户的token是否由自己亲手签发的
我们编写登录处理程序loginRecv.php用来处理通过post提交的用户名与密码,用户登录程序写明了用户第一次登录要做的事情,即签发token。
然后我们编写登录验证页面loginSuccessPage.php来验证用户身份信息
最后我们编写注销程序logoutRecv.php来处理用户的注销请求
这里加密函数使用的是php自带的crypt()
函数,密钥为$1$114#^514$
查询数据库并且分发token,这里使用密钥$1$114#^514$
(即salt变量)来加密用户信息以制造token,
$input_username = $_POST["username"];
$input_password = $_POST["password"];
$sql_server = "localhost";
$sql_username = "root";
$sql_password = "";
$sql_database = "mydb";
$con = new mysqli($sql_server, $sql_username, $sql_password, $sql_database);
$sql = 'SELECT * FROM user_account WHERE username="' . $input_username . '"';
$res = $con->query($sql);
// 用户不存在
if($res==null || $res->num_rows==0) {
echo "user not found";
}
// 一个用户存在
else if($res->num_rows==1) {
$user = $res->fetch_assoc();
// 密码是否正确
if($user["password"]==$input_password) {
// 用户名存入cookie
setcookie("username", $input_username, time()+99*365*24*60*60);
// 加密后的用户名作为token存入cookie
$salt = "$1$114#^514$";
$token = crypt($input_username, $salt);
setcookie("token", $token, time()+99*365*24*60*60);
echo "login success";
} else {
echo "wrong password";
}
}
// 多个用户存在 查询出错
else if($res->num_rows>1) {
echo "error";
}
?>
使用相同的密钥再次制造token,并且比对,从而判断用户信息。
// 检查是否携带cookie
if(!isset($_COOKIE["username"])) {
die("请先登录");
}
// 使用相同密钥再次制造token并验证
$salt = "$1$114#^514$";
$token = crypt($_COOKIE["username"], $salt);
if($token!=$_COOKIE["token"]) {
die("token验证失败, 登录失败");
}
?>
<!DOCTYPE html>
<html>
<head>
<title>login success page</title>
<meta charset="utf-8">
</head>
<body>
<h1>登录成功页面</h1>
<button onclick="javascrtpt:window.location.href='logoutRecv.php'">注销</button><br>
echo "欢迎: " . $_COOKIE["username"] . "
";
?>
<a href="subPage1.php">页面1</a>
</body>
</html>
直接将cookie设置为过期以销毁cookie即可
// 检查是否携带cookie
if(!isset($_COOKIE["username"])) {
die("请先登录");
}
// 销毁cookie
setcookie("username", "", time()-99);
setcookie("token", "", time()-99);
echo "";
?>
我们在登录页面登录
然后我们跳转到登录成功页面,发现登录成功页面校验了我们的token,并且通过了校验,成功判断用户的身份
点击注销,我们返回了登录页面