Cookie和Session的区别,Koa2+Mysql+Redis实现登录逻辑
数据库小白一枚,正在不断学习积累知识,现将学习到的知识记录一下,也是将我的所得分享给大家!而今天这篇文章《Cookie和Session的区别,Koa2+Mysql+Redis实现登录逻辑》带大家来了解一下Cookie和Session的区别,Koa2+Mysql+Redis实现登录逻辑,希望对大家的知识积累有所帮助,从而弥补自己的不足,助力实战开发!
为什么需要登录态?
因为需要识别用户是谁,否则怎么在网站上看到个人相关信息呢?
为什么需要登录体系?
因为HTTP是无状态的,什么是无状态呢?
就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。
我们的网站都是靠HTTP请求服务端获得相关数据,因为HTTP是无状态的,所以我们无法知道用户是谁。
所以我们需要其他方式保障我们的用户数据。
当然了,这种无状态的的好处是快速。
什么叫保持登录状态?
比如说我在百度A页面进行了登录,但是不找个地方记录这个登录态的话。
那我去B页面,我的登录态怎么保持呢?难道要url携带吗?这肯定是不安全的。你让用户再登录一次?登个鬼,再见? 用户体验不友好。
所以我们需要找个地方,存储用户的登录数据。这样可以给用户良好的用户体验。但是这个状态一般是有保质期的,主要原因也是为了安全。
为了解决这个问题,Cookie出现了。
Cookie
Cookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。
Cookie是存在浏览器端的。也就是可以存储我们的用户信息。一般Cookie 会根据从服务器端发送的响应的一个叫做Set-Cookie的首部字段信息,
通知浏览器保存Cookie。当下次发送请求时,会自动在请求报文中加入Cookie 值后发送出去。当然我们也可以自己操作Cookie。
如下图所示(图来源《图解HTTP》)


这样我们就可以通过Cookie中的信息来和服务端通信。
服务端如何配合?Session!
需要看起来Cookie已经达到了保持用户登录态的效果。但是Cookie中存储用户信息,显然不是很安全。所以这个时候我们需要存储一个唯一的标识。这个标识就像一把钥匙一样,比较复杂,看起来没什么规律,也没有用户的信息。只有我们自己的服务器可以知道用户是谁,但是其他人无法模拟。
这个时候Session就出现了,Session存储用户会话所需的信息。简单理解主要存储那把钥匙Session_ID,用这个钥匙Session_ID再去查询用户信息。但是这个标识需要存在Cookie中,所以Session机制需要借助于Cookie机制来达到保存标识Session_ID的目的。
如下图所示。

这个时候你可能会想,那这个Session有啥用?生成了一个复杂的ID,在服务器上存储。那好像我们自己生成一个Session_ID,存在Mysql也可以啊!没错,就是这样!
个人认为Session其实已经发展为一个抽象的概念,已经形成了业界的一种解决方案。可能它最开始出现的时候有自己规则,但是现在经过发展。随着业务的复杂,各大公司早就自己实现了方案。
Session_id你想搞成什么样,就什么样,想存在哪里就存在哪里。
一般服务端会把这个Session_id存在缓存,不会和用户信息表混在一起。一个是为了快速拿到Session_id。第二个是因为前面也讲到过,Session_id是有保质期的,为了安全一段时间就会失效,所以放在缓存里就可以了。常见的就是放在redis、memcached里。也有一些情况放在mysql里的,可能是用户数据比较多。但都不会和用户信息表混在一起。
Cookie 和 Session 的区别

登录态保持总结
- 浏览器第一次请求网站, 服务端生成 Session ID。
- 把生成的 Session ID 保存到服务端存储中。
- 把生成的 Session ID 返回给浏览器,通过 set-cookie。
- 浏览器收到 Session ID, 在下一次发送请求时就会带上这个 Session ID。
- 服务端收到浏览器发来的 Session ID,从 Session 存储中找到用户状态数据,会话建立。
- 此后的请求都会交换这个 Session ID,进行有状态的会话。
登录流程图

实现案例(koa2+ Mysql)
本案例适合对服务端有一定概念的同学哦,下面仅是核心代码。
数据库配置
第一步就是进行数据库配置,这里我单独配置了一个文件。
因为当项目大起来,需要对开发环境、测试环境、正式的环境的数据库进行区分。
let dbConf = null; const DEV = { database: 'dandelion', //数据库 user: 'root', //用户 password: 'xxx', //密码 port: '3306', //端口 host: '127.0.0.1' //服务ip地址 } 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, }) let query = function( sql, values ) { return new Promise(( resolve, reject ) => { pool.getConnection(function(err, connection) { if (err) { reject( err ) } else { connection.query(sql, values, ( err, rows) => { if ( err ) { reject( err ) } else { resolve( rows ) } connection.release() }) } }) }) } module.exports = { query, }
路由配置
这里我也是单独抽离出了文件,让路由看起来更舒服,更加好管理。
const Router = require('koa-router'); const router = new Router(); const koaCompose = require('koa-compose'); const {login} = require('../controllers/login'); // 加前缀 router.prefix('/api'); module.exports = () => { // 登录 router.post('/login', login); return koaCompose([router.routes(), router.allowedMethods()]); }
中间件注册路由。
const routers = require('../routers'); module.exports = (app) => { app.use(routers()); }
Session_id的生成和存储
我的session_id生成用了
const Redis = require("ioredis"); const { Store } = require("koa-session2"); class RedisStore extends Store { constructor() { super(); this.redis = new Redis(); } async get(sid, ctx) { let data = await this.redis.get(`SESSION:${sid}`); return JSON.parse(data); } async set(session, { sid = this.getID(24), maxAge = 1000 * 60 * 60 } = {}, ctx) { try { console.log(`SESSION:${sid}`); // Use redis set EX to automatically drop expired sessions await this.redis.set(`SESSION:${sid}`, JSON.stringify(session), 'EX', maxAge / 1000); } catch (e) {} return sid; } async destroy(sid, ctx) { return await this.redis.del(`SESSION:${sid}`); } } module.exports = RedisStore;
入口文件(index.js)
const Koa = require('koa'); const middleware = require('./middleware'); //中间件,目前注册了路由 const session = require("koa-session2"); // session const Store = require("./utils/Store.js"); //redis const body = require('koa-body'); const app = new Koa(); // session配置 app.use(session({ store: new Store(), key: "SESSIONID", })); // 解析 post 参数 app.use(body()); // 注册中间件 middleware(app); const PORT = 3001; // 启动服务 app.listen(PORT); console.log(`server is starting at port ${PORT}`);
登录接口实现
这里主要是根据用户的账号密码,拿到用户信息。然后将用户uid存储到session中,并将session_id设置到浏览器中。代码很少,因为用了现成的库,人家都帮你做好了。
这里我没有把session_id设置过期时间,这样用户关闭浏览器就没了。
const UserModel = require('../model/UserModel'); //用户表相关sql语句 const userModel = new UserModel(); /** * @description: 登录接口 * @param {account} 账号 * @param {password} 密码 * @return: 登录结果 */ async function login(ctx, next) { // 获取用户名密码 get const {account, password} = ctx.request.body; // 根据用户名密码获取用户信息 const userInfo = await userModel.getUserInfoByAccount(account, password); // 生成session_id ctx.session.uid = JSON.stringify(userInfo[0].uid); ctx.body = { mes: '登录成功', data: userInfo[0].uid, success: true, }; }; module.exports = { login, };
登录之后其他的接口就可以通过这个session_id获取到登录态。
// 业务接口,获取用户所有的需求 const DemandModel = require('../../model/DemandModel'); const demandModel = new DemandModel(); const shortid = require('js-shortid'); const Store = require("../../utils/Store.js"); const redis = new Store(); async function selectUserDemand(ctx, next) { // 判断用户是否登录,获取cookie里的SESSIONID const SESSIONID = ctx.cookies.get('SESSIONID'); if (!SESSIONID) { console.log('没有携带SESSIONID,去登录吧~'); return false; } // 如果有SESSIONID,就去redis里拿数据 const redisData = await redis.get(SESSIONID); if (!redisData) { console.log('SESSIONID已经过期,去登录吧~'); return false; } if (redisData && redisData.uid) { console.log(`登录了,uid为${redisData.uid}`); } const uid = JSON.parse(redisData.uid); // 根据session里的uid 处理业务逻辑 const data = await demandModel.selectDemandByUid(uid); console.log(data); ctx.body = { mes: '', data, success: true, }; }; module.exports = { selectUserDemand, }
坑点注意注意
1、注意跨域问题
2、处理OPTIONS多发预检测问题
app.use(async (ctx, next) => { ctx.set('Access-Control-Allow-Origin', 'http://test.xue.com'); ctx.set('Access-Control-Allow-Credentials', true); ctx.set('Access-Control-Allow-Headers', 'content-type'); ctx.set('Access-Control-Allow-Methods', 'OPTIONS, GET, HEAD, PUT, POST, DELETE, PATCH'); // 这个响应头的意义在于,设置一个相对时间,在该非简单请求在服务器端通过检验的那一刻起, // 当流逝的时间的毫秒数不足Access-Control-Max-Age时,就不需要再进行预检,可以直接发送一次请求。 ctx.set('Access-Control-Max-Age', 3600 * 24); if (ctx.method == 'OPTIONS') { ctx.body = 200; } else { await next(); } });
3、允许携带cookie
发请求的时候设置这个参数
axios({ url: 'http://test.xue.com:3001/api/login', method: 'post', data: { account: this.account, password: this.password, }, withCredentials: true, // 允许设置凭证 }).then(res => { console.log(res.data); if (res.data.success) { this.$router.push({ path: '/index' }) } })
源码
以上的代码只是贴了核心的,源码如下
但是练手的项目还在开发中,网站其他功能还没有全部实现。
代码写的比较挫???
但是登录完全没有问题的~
但是你需要提前了解Redis、Mysql、Nginx和基本的服务器操作哦!
如有错误,请指教?
今天带大家了解了MySQL、Redis、前端、Node.js、koa2的相关知识,希望对你有所帮助;关于数据库的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~

- 上一篇
- PHP面试MySQL数据库的索引

- 下一篇
- 内网穿透与反向代理,浅谈前后台分离
-
- 生动的抽屉
- 很有用,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者大大分享技术文章!
- 2023-05-03 06:24:16
-
- 冷静的画板
- 这篇技术贴出现的刚刚好,好细啊,很棒,已加入收藏夹了,关注师傅了!希望师傅能多写数据库相关的文章。
- 2023-05-02 15:59:57
-
- 细腻的大山
- 这篇博文出现的刚刚好,太细致了,受益颇多,码起来,关注作者了!希望作者能多写数据库相关的文章。
- 2023-03-19 13:03:01
-
- 数据库 · MySQL | 14小时前 | 索引 数据类型 字符集 存储引擎 CREATETABLE
- MySQL新建表操作指南与建表技巧
- 462浏览 收藏
-
- 数据库 · MySQL | 1个月前 | 条件判断
- CASEWHEN条件判断的嵌套使用详解与实战场景分析
- 469浏览 收藏
-
- 数据库 · MySQL | 1个月前 | java php
- CSV文件批量导入MySQL的性能优化秘籍大揭秘
- 289浏览 收藏
-
- 数据库 · MySQL | 1个月前 |
- GaleraCluster多主集群配置与冲突解决攻略
- 239浏览 收藏
-
- 数据库 · MySQL | 1个月前 | 窗口函数实战
- MySQL窗口函数实战案例深度剖析
- 315浏览 收藏
-
- 数据库 · MySQL | 1个月前 | 自定义函数
- MySQL插件开发入门:自定义函数(UDF)编写指南
- 184浏览 收藏
-
- 数据库 · MySQL | 1个月前 |
- Windows系统MySQL8.0免安装版配置攻略
- 227浏览 收藏
-
- 数据库 · MySQL | 1个月前 | MySQL错误 数据库诊断
- 深度解析错误代码1045/1217/1205的根本原因及解决方案
- 202浏览 收藏
-
- 数据库 · MySQL | 1个月前 | sql注入 编码规范
- 防范SQL注入必备:编码规范与工具推荐指南
- 140浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 笔灵AI生成答辩PPT
- 探索笔灵AI生成答辩PPT的强大功能,快速制作高质量答辩PPT。精准内容提取、多样模板匹配、数据可视化、配套自述稿生成,让您的学术和职场展示更加专业与高效。
- 14次使用
-
- 知网AIGC检测服务系统
- 知网AIGC检测服务系统,专注于检测学术文本中的疑似AI生成内容。依托知网海量高质量文献资源,结合先进的“知识增强AIGC检测技术”,系统能够从语言模式和语义逻辑两方面精准识别AI生成内容,适用于学术研究、教育和企业领域,确保文本的真实性和原创性。
- 22次使用
-
- AIGC检测-Aibiye
- AIbiye官网推出的AIGC检测服务,专注于检测ChatGPT、Gemini、Claude等AIGC工具生成的文本,帮助用户确保论文的原创性和学术规范。支持txt和doc(x)格式,检测范围为论文正文,提供高准确性和便捷的用户体验。
- 30次使用
-
- 易笔AI论文
- 易笔AI论文平台提供自动写作、格式校对、查重检测等功能,支持多种学术领域的论文生成。价格优惠,界面友好,操作简便,适用于学术研究者、学生及论文辅导机构。
- 40次使用
-
- 笔启AI论文写作平台
- 笔启AI论文写作平台提供多类型论文生成服务,支持多语言写作,满足学术研究者、学生和职场人士的需求。平台采用AI 4.0版本,确保论文质量和原创性,并提供查重保障和隐私保护。
- 35次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 分享Redis高可用架构设计实践
- 2023-01-24 286浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览