状态管理(了解、后端)
由于是无状态协议,为了保障安全,就需要cookie和session。
它按照下面的流程来认证客户端的身份
-
客户端登录成功后,服务器会给客户端一个出入证(令牌 token)
-
后续客户端的每次请求,都必须要附带这个出入证(令牌 token)
cookie
存基础用户名,当需要访问重要的东西,还需要再次验证登录等操作。每访问一次网站,都会给客户端一个cookie,存在客户端的卡包中(可以存放多个,可以自动出示,可以准确出示,可以自动管理,过期自动移除),是全部以键值存在的,等下次进入就会识别到cookie是那个用户。
当浏览器向服务器发送一个请求的时候,它会瞄一眼自己的卡包,看看哪些卡片适合附带捎给服务器。
条件(同时满足)
1、cookie没有过期
2、cookie中的域和这次请求的域是匹配的
3、cookie中的path和这次请求的path是匹配的
4、验证cookie的安全传输
-
如果cookie的secure属性是true,则请求协议必须是
https
,否则不会发送该cookie -
如果cookie的secure属性是false,则请求协议可以是
http
,也可以是https
具体加入的方式是,浏览器会将符合条件的cookie,自动放置到请求头中,
cookie中包含了重要的身份信息,永远不要把你的cookie泄露给别人!!!否则,他人就拿到了你的证件,有了证件,就具备了为所欲为的可能性
cookie 中间件
步骤:
1、安装中间件。
脚手架一般都自动已经生成了。
npm i cookie-parser
2、在登录成功时,给浏览器一个cookie。
/* 登录 */
router.get('/login', async function (req, res ) {
let {name,psw} = req.query; //获取请求数据
let arr= await serviceAdim.find();//得到数据库所有账号密码
let result = arr.filter(item => item.name == name && item.psw == psw)[0]; //得到结果
if(result){
//cookie的保存是 键值对 key=value
// res.header("set-cookie",`token=${result._id};path=/;domain=127.0.0.1;max-age=3600;httpOnly`);
//如果有返回值,则说明登录成功
//将用户id保存到cookie中
//使用cookie-parser中间件的方法
//参数一:cookie名称
//参数二:cookie的值
//参数三:可选配置对象
res.cookie("token", result._id, {
path: "/",
domain: "127.0.0.1",
maxAge: 7 * 24 * 3600 * 1000, //毫秒数,可以不写,就默认是浏览器会话时间。
});
res.send(resultUtil(result,0,"登录成功",0));
}
else{
res.send(resultUtil(result,0,"登录失败",1));
}
});
3、每次使用需要验证,需要一个专门进行验证的文件来进行判断。
注意:路由地址是/user/:id,但是用户真实传过来的地址是/user/1 /user/2,因此需要一个转换路径的中间件,path-to-regexp。
注:先下载在引入,中间件引入格式必须是var{pathToRegexp} = require("path-to-regexp");不然接收不到。
var{pathToRegexp} = require("path-to-regexp");
/* 判断每次请求,对应user里的操作 */
let needToToken = [
{method:"GET",path:"/user"},///user是路由里面设置的路径入口
{method:"GET",path:"/user/:id"},
{method:"PUT",path:"/user/:id"},
{method:"DELETE",path:"/user/:id"},
{method:"POST",path:"/user/add"},
];
//注意:路由地址是/user/:id
//但是用户真实传过来的地址是/user/1 /user/2
module.exports = function(req,res,next){
console.log(req.method,req.path);//得到请求的方式和路径
let apis = needToToken.filter(item=>{
let reg = pathToRegexp(item.path);//把路径 /user/2转换为/user/:id
//验证地址是不是需要验证的地址
let flag = reg.test(req.path);
return item.method === req.method && flag;
});
//请求的地址在验证的数据里没有,就直接通过
if(apis.length <= 0){
next();
return;
}
//得到登录的token
let token = req.cookies.token;
if (!token) {//如果没有就需要重新登录
res.send({code:1,msg:"请先登录",data:null});
return;
}
next();//有就通过。
}
4、在总的路由文件引入这个验证文件。
const tokenMiddleware = require("./tokenMiddleware.js");
router.use(tokenMiddleware);
5、在识别没有cookie码直接跳转到登录注册页面。
如果通过地址栏直接进入导航,需要进行判断,因为cookie全是;分割的键值对需要转成数组,再去查找是否有一个叫Token的键,然后的它对应的值,就是id,有的化就和登录一样,进行验证,只不过通过比对Id,如果没有就直接跳转到登录页面。
handle(){
let layer = layui.layer;
let token = "";
let cookies = document.cookie;//得到所有cookie,获取的到的cookie可能不止一个,每一个cookie是用;进行分割的,token=xxx;user=xxxx;id=xxxx;
let cookieArr = cookies.split(";");//转换成数组
for(let i=0; i<cookieArr.length; i++){
let item = cookieArr[i];
let arr = item.split("="); //每一个cookie都是一个键值对 xxx=xxxxx,在分割成数组
if(arr[0] === "token"){//键等于token,就代表有,然后把后面的赋给声明的字符串。
token = arr[1];
break;
}
}
if(!token){ //如果token都没有,那么肯定没有登录,就跳转到登录
location.hash = "#/login";
return;
}
else{
$.ajax({//如果有token当然还需要到后端进行验证
url:"/api/adim/verify/woami",
type:"GET",
success:function(res){
if(res.code === 0){
$("#username").text(res.data.name);//把登录名给添加在页面上
}
else{
location.hash = "#/login";
}
}
});
/* 点击退出 */
$("#logout").on("click",function(){
$.ajax({
url:"/api/adim/verify/loginOut",
type:"GET",
success:function(res){
if(res.code === 0){
alert("退出成功")
location.hash = "#/login";
}
}
});
});
}
}
6、同样在后端路由里面也需要相应的设置。
由于都是 router.get,为了区分需要加一个分路径,不然会识别进入上面的注册路由,得到请求过来的token,没有就跳转登录,有就比对Id验证。
/* 验证登录 路径要改一下,不然会默认是上面的注册*/
router.get("/verify/woami",async function(req,res,next){
let token = req.cookies.token;
if(!token){//没有token
res.send({code:1,msg:"请先登录",data:null});
}
else{
let arr= await serviceAdim.find();//得到数据库所有账号密码
let result = arr.filter(item => item._id == token)[0]; //得到结果
console.log(result);
if(!result){
res.send({code:1,msg:"请先登录",data:null});
}
else{
res.send({code:0,msg:"验证成功",data:result});
}
}
});
7、退出。
同样也是导航页面的事件,然后再路由也需要一个相应路径,在后台进行操作。
前台
$("#logout").on("click",function(){
$.ajax({ url:"/api/adim/verify/loginOut",
type:"GET",
success:function(res){
if(res.code === 0){
alert("退出成功")
location.hash = "#/login";
}
}
});
});
后台
/* 退出 */
router.get("/verify/loginOut",function(req,res){
res.cookie("token", ""(这里token的值只接改成空字符串), {
path: "/",
domain: "127.0.0.1",
maxAge: -1, //改成-1
});
res.send({code:0,msg:"退出成功",data:null});
});
session(前端一般拿不到这个值,只能得到session id)
不能设置保存时间,是浏览器会话时间。
cookie 虽然很方便,但是使用 cookie 中的所有数据在客户端就可以被修改,数据非常容易被伪造,那么一些重要的数据就不能存放在 cookie 中了,而且如果 cookie 中数据字段太多会影响传输效率。为了解决这些问题,就产生了 session,session 中的数据是保留在服务器端的。
原理:
通过浏览器访问,后台生成一个id和内容,把随机生成的id发给浏览器,但是实际上的具体信息都存在后台,浏览器得到的只是一个随机的id,当会话结果,id自动失效,这样就保障了信息的安全性。
1、后端安装。
npm i express-session
2、在后端入口文件并要引用并设置。
//引入express-session中间件
const session = require("express-session");
app.use(session({
secret:"lovo",
name:"sessionid",
resave:true,
saveUninitialized:true
}));
3、当用户登录成功之后,就可以记录session信息,因此在登录的路由代码中加入session。
router.post('/login', async function (req, res) {
let {name,psw} = req.body; //获取请求数据
let arr= await serviceAdim.find();//得到数据库所有账号密码
let result = arr.filter(item => item.name == name && item.psw == psw)[0]; //得到结果
if(result){
/* session方式 */
req.session.loginUser = result;//loginUser自己取名
res.send({code:0,msg:"登录成功",data:result});
}
else{
res.send({code:1,msg:"登录失败",data:null});
}
});
4、在需要验证的路由中,判断是否有session,并且session对象中是否有loginUser对象即可,我们可以在之前的中间件中,直接处理session。
/* session处理方式 */
if (req.session && req.session.loginUser) {//进入中间件判断,是否有.loginUser
//说明已经登录过了
next();
} else {
res
.status(403)
.send("你没有权限这么做,请点击<a href='http://127.0.0.1:8888/login.html'>这里</a>先登录");
return;
}