Cookie&Session,登录的那点事儿

栏目: 数据库 · 发布时间: 4年前

内容简介:因为HTTP是无状态的,什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。我们的网站都是靠HTTP请求服务端获得相关数据,因为HTTP是无状态的,所以我们无法知道用户是谁。

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

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

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

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

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

什么叫保持登录状态?

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

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

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

Cookie

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

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

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

Cookie&Session,登录的那点事儿
Cookie&Session,登录的那点事儿

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

服务端如何配合?Session!

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

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

Cookie&Session,登录的那点事儿

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

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

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

一般服务端会把这个Session_id存在缓存,不会和用户信息表混在一起。一个是为了快速拿到Session_id。第二个是因为前面也讲到过,Session_id是有保质期的,为了安全一段时间就会失效,所以放在缓存里就可以了。常见的就是放在 redismemcached 里。也有一些情况放在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,进行有状态的会话。

登录流程图

Cookie&Session,登录的那点事儿

实现案例(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'
        })
    }
})
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Algorithmic Beauty of Plants

The Algorithmic Beauty of Plants

Przemyslaw Prusinkiewicz、Aristid Lindenmayer / Springer / 1996-4-18 / USD 99.00

Now available in an affordable softcover edition, this classic in Springer's acclaimed Virtual Laboratory series is the first comprehensive account of the computer simulation of plant development. 150......一起来看看 《The Algorithmic Beauty of Plants》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具