顺晟科技
2021-06-16 10:38:25
307
为什么我需要登录状态?
因为你需要识别用户是谁,不然怎么能在网站上看到个人信息?
为什么需要登录系统?
因为HTTP是无状态的,什么是无状态?
也就是说,这个请求和上一个请求没有关系,他们不认识,也没有关系。
我们的网站都是靠HTTP请求服务器获取相关数据。因为HTTP是无状态的,所以我们无法知道用户是谁。
所以我们需要其他方法来保护我们的用户数据。
当然,这种无国籍状态的好处是快速。
什么叫一直登录?
比如我登录了百度A页面,但是没有找到记录登录状态的地方。
去B页怎么保持登录状态?您想通过网址携带它吗?这不安全。你要求用户重新登录?去死吧。再见。用户体验不友好。
所以我们需要找到一个地方来存储用户的登录数据。这样可以给用户很好的用户体验。但这种状态通常是有保质期的,主要是为了安全。
为了解决这个问题,出现了Cookie。
饼干
Cookie是解决HTTP协议无状态缺陷的一种努力。
Cookie存在于浏览器端。也就是说,我们可以存储我们的用户信息。通常,Cookie基于从服务器发送的响应的标题字段信息,称为设置Cookie,
告诉浏览器保存Cookie。下次发送请求时,Cookie值将自动添加到请求消息中,然后发送出去。当然也可以自己操作Cookie。
如下图所示(来源《图解HTTP》)
饼干
图像
这样,我们就可以通过Cookie中的信息与服务器进行通信。
服务器如何配合?会话!
看来Cookie达到了让用户保持登录的效果。但是在Cookie中存储用户信息显然不是很安全。所以此时,我们需要存储一个的标识符。这个logo就像一把钥匙,复杂,看起来不规则,没有用户信息。只有我们自己的服务器才能知道用户是谁,别人无法模拟。
此时,会话出现,会话存储用户会话所需的信息。简单理解主要是存储key Session_ID,然后用这个key Session_ID查询用户信息。但是这个标识符需要存在于cookie中,所以Session机制需要cookie机制来保存标识符Session_ID。
如下图所示。
会议
这时你可能会想,这一届有什么用?生成一个复杂的标识并存储在服务器上。好像可以自己生成一个Session_ID,Mysql也可以存在!对,就是这样!
个人认为Session已经发展成一个抽象的概念,在行业内形成了解决方案。可能刚出现的时候有自己的规则,现在发展起来了。随着业务的复杂化,各大公司已经实现了自己的计划。
Session_id:无论你想做什么,都可以存在于你想存在的任何地方。
通常,服务器会将这个Session_id存储在缓存中,不会与用户信息表混合。一种是快速获取Session_id。第二,前面提到过,Session_id是有保质期的,为了安全,过一段时间就会过期,所以可以存放在缓存中。Common放在redis,memcached。mysql中也有一些情况,可能是用户数据比较多。但是它们并没有和用户信息表混在一起。
Cookie和会话的区别
Cookie和会话的区别
登录状态保存摘要
浏览器次请求网站,服务器生成Session ID。
将生成的会话标识保存到服务器存储中。
生成的会话标识通过设置cookie返回给浏览器。
当浏览器收到会话标识时,它将在下次发送请求时携带该会话标识。
服务器接收浏览器发送的会话标识,从会话存储中找到用户状态数据,并建立会话。
后续请求会将此会话标识交换为有状态会话。
登录流程图
登录流程图
实现案例(koa2 Mysql)
本案例适合对服务端有一定概念的同学哦,下面仅是核心代码。
数据库配置
步就是进行数据库配置,这里我单独配置了一个文件。
因为当项目大起来,需要对开发环境、测试环境、正式的环境的数据库进行区分。
让dbConf=null
const DEV={
数据库: '蒲公英',//数据库
user: 'root ',//用户
password: 'xxx ',//密码
port: '3306 ',//端口
host: '127.0.0.1' //服务互联网协议(互联网协议的缩写)地址
}
dbConf=DEV
module.exports=dbConf
数据库连接。
const MySQL=require(' MySQL ');
const dbConf=require(' ././config/DBConf ');
const pool=mysql.createPool({
host: dbConf.host,
user: dbConf.user,
password: dbConf.password,
database: dbConf.database,
})
让query=function(sql,values ) {
返回新承诺((解决,拒绝)={
pool.getConnection(函数(错误,连接){
if (err) {
拒绝(错误)
} else {
connection.query(sql,values,(err,row)={
if (err ) {
拒绝(错误)
} else {
解析(行)
}
connection.release()
})
}
})
})
}
module.exports={
查询,
}
路由配置
这里我也是单独抽离出了文件,让路由看起来更舒服,更加好管理。
const Router=require(' KOA-Router ');
const Router=new Router();
const Koacompose=require(' KOA-compose ');
const {login}=require('./controllers/log in’);
//加前缀
路由器。前缀('/API ');
module.exports=()={
//登录
router.post('/login ',登录);
返回Koacompose([路由器。routes(),路由器。allowedmethods()]);
}
中间件注册路由。
const routers=require('./路由器');
module.exports=(app)={
app。use(routers());
}
会话编号的生成和存储
我的会话编号生成用了koa-session2库,存储是存在存储里的,用了一个ioredis库。
配置文件。
const Redis=require(' iore dis ');
const { Store }=require(' KOA-session 2 ');
类RedisStore扩展了商店{
constructor() {
super();
这个。Redis=new Redis();
}
异步获取(sid,ctx) {
让data=等待。redis。get(` session : $ { sid } `);
返回JSON.parse(数据);
}
异步集(会话,{ sid=this.getID(24),maxAge=1000 * 60 * 60 }={},ctx) {
尝试{
控制台。日志(` session : $ { sid } `);
//使用redis set EX自动删除过期的会话
等等这个。redis。set(` session : $ { sid } `,JSON.stringify(session),' EX ',MaxAge/1000);
} catch (e) {}
返回(同suddenionosphericdisturbance)电离层的突然骚扰
}
异步销毁(sid,ctx) {
回来等等这个。redis。del(` session : $ { sid } `);
}
}
module.exports=RedisStore
入口文件(index.js)
const Koa=require(' Koa ');
常数中间件=require(' ./中间件');//中间件,目前注册了路由
const session=require(' KOA-session 2 ');//会话
const Store=require ' ./utils/store。js’);//redis
const body=require(' KOA-body ');
const app=new Koa();
//会话配置
app.use(会话({
store: new Store(),
key: 'SESSIONID ',
}));
//解析邮政参数
app。use(body());
//注册中间件
中间件(app);
const PORT=3001
//启动服务
app。倾听(PORT);
console.log(`server从端口${PORT} `)开始);
登录接口实现
这里主要是根据用户的账号密码,拿到用户信息。然后将用户用户界面设计(用户界面设计的缩写)存储到会议中,并将会话编号设置到浏览器中。代码很少,因为用了现成的库,人家都帮你做好了。
这里我没有把会话编号设置过期时间,这样用户关闭浏览器就没了。
const UserModel=require('./model/user model’);//用户表相关结构化查询语言语句
const用户模型=新用户模型();
/**
* @description:登录接口
* @param {account}账号
* @param {password}密码
* @return:登录结果
*/
异步函数登录(ctx,下一个){
//获取用户名密码得到
const {帐户,密码}=CTX。请求。身体;
//根据用户名密码获取用户信息
const userInfo=等待用户模型。getuserinfobyaccount(帐号,密码);
//生成会话编号
CTX。会话。uid=JSON。字符串(用户信息[0]).uid);
ctx.body={
" mes: "登录成功,
数据:用户信息[0].uid,
success: true,
};
};
module.exports={
登录,
};
登录之后其他的接口就可以通过这个会话编号获取到登录态。
//业务接口,获取用户所有的需求
const DemandModel=require('././model/DemandMoDEL’);
const DemandMoDEL=new DemandMoDEL();
const short id=require(' js-short id ');
常量存储=要求('././utils/store。js’);
const redis=new Store();
异步功能选择用户需求(ctx,下一步){
//判断用户是否登录,获取甜饼干里的便会失效
CTX。饼干。get(' SESSIONID ');
if(!SESSIONID) {
console.log('没有携带SESSIONID,去登录吧~');
返回错误的
}
//如果有SESSIONID,就去存储里拿数据
const redis data=wait redis。get(SESSIONID);
if(!redisData) {
console.log('SESSIONID已经过期,去登录吧~');
返回错误的
}
if (redisData redisData.uid) {
控制台。日志(` 0登录了,uid为$ { redis数据。uid } `);
}
const uid=JSON。解析(RedIsdata。uid);
//根据会议里的用户界面设计(用户界面设计的缩写)处理业务逻辑
const data=等待需求模型。selectdemandbyuid(uid);
console.log(数据);
ctx.body={
mes: ' ',
数据,
success: true,
};
};
module.exports={
选择用户需求,
}
坑点注意注意
1、注意跨域问题
2、处理选择多发预检测问题
app.use(async (ctx,next)={
CTX。设置('访问控制-允许-原始',' http://测试。薛。com ');
' ctx.set('访问控制-允许-凭证,true);
' ctx.set('访问控制-允许-标题','内容类型');
设置('访问控制允许方法','选项,获取,头,放,后,删除,补丁');
//这个响应头的意义在于,设置一个相对时间,在该非简单请求在服务器端通过检验的那一刻起,
//当流逝的时间的毫秒数不足访问控制更大年龄时,就不需要再进行预检,可以直接发送一次请求。
' ctx.set('访问控制-更大年龄',3600 * 24);
if (ctx.method=='OPTIONS') {
ctx.body=200
} else {
wait next();
}
});
3、允许携带甜饼干
发请求的时候设置这个参数如果:为真,请求才能携带甜饼干
axios({
URL : ' http://测试。徐。com :3001/API/登录',
方法: '发布,
数据: {
这个账户,
密码:这个.密码,
},
如果:为真,//允许设置凭证
}).然后(res={
控制台。日志(资源数据);
if (res.data.success) {
这个$router.push({
路径: '/索引'
})
}
})
03
2022-06
17
2022-03
17
2022-03
16
2022-03
28
2021-08
16
2021-06