美文网首页
Cookie和Session的区别,Koa2+Mysql+Red

Cookie和Session的区别,Koa2+Mysql+Red

作者: YukiYang | 来源:发表于2019-03-05 20:16 被阅读0次

    为什么需要登录态?

    因为需要识别用户是谁,否则怎么在网站上看到个人相关信息呢?

    为什么需要登录体系?

    因为HTTP是无状态的,什么是无状态呢?

    就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。

    我们的网站都是靠HTTP请求服务端获得相关数据,因为HTTP是无状态的,所以我们无法知道用户是谁。

    所以我们需要其他方式保障我们的用户数据。

    当然了,这种无状态的的好处是快速。

    什么叫保持登录状态?

    比如说我在百度A页面进行了登录,但是不找个地方记录这个登录态的话。
    那我去B页面,我的登录态怎么保持呢?难道要url携带吗?这肯定是不安全的。你让用户再登录一次?登个鬼,再见👋 用户体验不友好。

    所以我们需要找个地方,存储用户的登录数据。这样可以给用户良好的用户体验。但是这个状态一般是有保质期的,主要原因也是为了安全。

    为了解决这个问题,Cookie出现了。

    Cookie

    Cookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。

    Cookie是存在浏览器端的。也就是可以存储我们的用户信息。一般Cookie 会根据从服务器端发送的响应的一个叫做Set-Cookie的首部字段信息,
    通知浏览器保存Cookie。当下次发送请求时,会自动在请求报文中加入Cookie 值后发送出去。当然我们也可以自己操作Cookie。

    如下图所示(图来源《图解HTTP》)


    Cookie
    image

    这样我们就可以通过Cookie中的信息来和服务端通信。

    服务端如何配合?Session!

    需要看起来Cookie已经达到了保持用户登录态的效果。但是Cookie中存储用户信息,显然不是很安全。所以这个时候我们需要存储一个唯一的标识。这个标识就像一把钥匙一样,比较复杂,看起来没什么规律,也没有用户的信息。只有我们自己的服务器可以知道用户是谁,但是其他人无法模拟。

    这个时候Session就出现了,Session存储用户会话所需的信息。简单理解主要存储那把钥匙Session_ID,用这个钥匙Session_ID再去查询用户信息。但是这个标识需要存在Cookie中,所以Session机制需要借助于Cookie机制来达到保存标识Session_ID的目的。
    如下图所示。

    Session

    这个时候你可能会想,那这个Session有啥用?生成了一个复杂的ID,在服务器上存储。那好像我们自己生成一个Session_ID,存在Mysql也可以啊!没错,就是这样!

    个人认为Session其实已经发展为一个抽象的概念,已经形成了业界的一种解决方案。可能它最开始出现的时候有自己规则,但是现在经过发展。随着业务的复杂,各大公司早就自己实现了方案。

    Session_id你想搞成什么样,就什么样,想存在哪里就存在哪里。

    一般服务端会把这个Session_id存在缓存,不会和用户信息表混在一起。一个是为了快速拿到Session_id。第二个是因为前面也讲到过,Session_id是有保质期的,为了安全一段时间就会失效,所以放在缓存里就可以了。常见的就是放在redis、memcached里。也有一些情况放在mysql里的,可能是用户数据比较多。但都不会和用户信息表混在一起。

    Cookie 和 Session 的区别

    Cookie 和 Session 的区别

    登录态保持总结

    1. 浏览器第一次请求网站, 服务端生成 Session ID。
    2. 把生成的 Session ID 保存到服务端存储中。
    3. 把生成的 Session ID 返回给浏览器,通过 set-cookie。
    4. 浏览器收到 Session ID, 在下一次发送请求时就会带上这个 Session ID。
    5. 服务端收到浏览器发来的 Session ID,从 Session 存储中找到用户状态数据,会话建立。
    6. 此后的请求都会交换这个 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生成用了koa-session2库,存储是存在redis里的,用了一个ioredis库。

    配置文件。

    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

    发请求的时候设置这个参数withCredentials: true,请求才能携带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和基本的服务器操作哦!

    如有错误,请指教😜

    相关文章

      网友评论

          本文标题:Cookie和Session的区别,Koa2+Mysql+Red

          本文链接:https://www.haomeiwen.com/subject/zaqauqtx.html