一探 koa-session 源码

2019-10-10 admin

引言

既然挖了坑就得把它填上,在之前的 Nuxt 鉴权一文中,讲述了如何使用 koa-session ,主要是配置和如何更改 session 中的内容,我们来回顾一下。这是配置文件

app.use(
  session(
    {
      key: "lxg",
      overwrite: true, //覆写Cookie
      httpOnly: true, //经允许通过 JS 来更改
      renew: true,//会话快到期时续订,可以保持用户一直登陆
      store: new MongooseStore({
        createIndexes: "appSessions",
        connection: mongoose,
        expires: 86400, // 默认一天
        name: "AppSession"
      }) //传入一个用于session的外部储存,我这里是使用了 mongodb
    },
    app
  )

这是登陆接口,使用 koa-session 修改session

 static async login(ctx) {
    let { passwd, email } = ctx.request.body;
    let hasuser = await UserModel.findOne({ email: email, passwd: md(passwd) });

    if (!hasuser) {
      return ctx.error({});
    } else {
      let userid = hasuser._id;
      const { session } = ctx;
      session.userid = userid;
      return ctx.success({ data: { userid: userid } });
    }
  }

可见配置好修改一下session还是非常简单的,知其然当然还是不够的,我们还得知其所以然,进入源码来一探 koa-session 的工作流程。

0.两种储存方式

在源代码中我们可以清晰看到,整个流程是分为了 使用和不使用外部存储(store)的,当没有设置 store 的时候,所有的 session 数据都是经过编码后由用户浏览器储存在 cookie 中, 而设置了 store 之后,数据都是储存在服务器的外部储存中,cookie 中只是储存了一个唯一用户标识符(externalKey),koa-session 只需要拿着这个钥匙去外部储存中寻找数据就可以了。相比与直接使用 Cookie 储存数据,使用 store 储存有两个优点

  • 数据大小没限制

    • 使用 cookie 会对cookie 大小有严格的限制,稍微多一点数据就放不下了
  • 数据更安全

    • 使用 Cookie 时,数据只是经过简单编码存放于 cookie,很容易就能反编码出真实数据,而且存放与用户本地,容易被其他程序窃取。

在实际应用中更推荐使用 store,当然数据非常简单而且不需要保密使用 cookie 也是可以的。

1.默认参数处理

理解本节需要的一些稍微高阶一点的 JS 知识,看不懂代码的可以先了解一下这些知识点,当然 koa 相关的概念也要了解一点。

语句 来源知识点
getter/setter ES5
Object.defineProperties/Object.hasOwnProperty Object对象的方法
symbol ES6

打开位于 node_modules 里的 koa-session 文件夹下的 index.js 文件 ,映入眼帘的就是这个主流程函数,接收一个 app(koa实例) 和 opt(配置文件) 作为参数

enter description here

其中第一个被调用的函数就是这个,传入参数是 opt。

enter description here

这个函数作用是使用用户设置的配置替换掉默认的配置。

2.创建 session 对象

下一个就是它,传入参数是实例上下文和配置参数

enter description here

这个函数做的所做的工作就是如果当前 context 没有设置 session 就新建一个。使用了 getter 当外界第一次调用这个属性的时候才创建了一个 ContextSession 对象。通过属性的引用关系我们可以得知,我们直接使用的 ctx.session 实际上是 ContextSession 对象

3.初始化外部储存

这一步是使用了外部储存才有的,使用了外部储存 session 就储存在外部储存中如数据库,缓存甚至文件中,cookie 中只负责储存一个唯一用户标识符,koa-session就拿这个标识符去外部储存中找数据,如果没有使用外部储存,所有的session数据就是经过简单编码储存在 cookie 中,这样既限制了储存容量也不安全。我们来看代码:

这个函数第一行就是创建了一个名为 sess 的 ContextSession 对象。

初始化外部储存

enter description here

大体来说就是判断是否有 externalKey , 没有的话就新建。这个 externalKey 是保存在 cookie 中唯一标识用户的一个字符串,koa-session 使用这个字符串在外部储存中查找对应的用户 session 数据

enter description here

重点是这句,将当前的 seeion 进行 hash 编码保存,在最后的时候进行 hash 的比较,如果 session 发生了更改就进行保存,至此完成初始化,保存下来了 session 的初始状态。

4.初始化 cookie

在主流程中我们并没有看到没有使用外部储存的情况下如何初始化 session ,其实这种情况下的初始化发生在业务逻辑中操作了 session 之后,例如:

 const { session } = ctx;
 session.userid = userid;

就会触发 ctx 的 session 属性拦截器,ctx.session 实际上是 sess 的 get 方法返回值:

enter description here

最终在 ContextSession 对象的 get 方法中执行 session 的初始化操作:

enter description here

可以看到没有外部储存的情况下执行了 this.initFromCookie()

 initFromCookie() {
    debug('init from cookie');
    const ctx = this.ctx;
    const opts = this.opts;

    const cookie = ctx.cookies.get(opts.key, opts);
    if (!cookie) {
      this.create();
      return;
    }

    let json;
    debug('parse %s', cookie);
    try {
      json = opts.decode(cookie);
    } catch (err) {
      // backwards compatibility:
      // create a new session if parsing fails.
      // new Buffer(string, 'base64') does not seem to crash
      // when `string` is not base64-encoded.
      // but `JSON.parse(string)` will crash.
      debug('decode %j error: %s', cookie, err);
      if (!(err instanceof SyntaxError)) {
        // clean this cookie to ensure next request won't throw again
        ctx.cookies.set(opts.key, '', opts);
        // ctx.onerror will unset all headers, and set those specified in err
        err.headers = {
          'set-cookie': ctx.response.get('set-cookie'),
        };
        throw err;
      }
      this.create();
      return;
    }

    debug('parsed %j', json);

    if (!this.valid(json)) {
      this.create();
      return;
    }

    // support access `ctx.session` before session middleware
    this.create(json);
    this.prevHash = util.hash(this.session.toJSON());
  }

其主要逻辑就只没有发现已有的 session 就新建一个 Session 对象并初始化。

enter description here

如果是第一次访问服务器将 isNew 设置为 true。

4.保存更改

当进行完我们的业务逻辑之后,调用 sess.commit 函数进行保存:

enter description here

主要是根据 hash 值判断session是否被更改,更改了的话调用 this.sava 进行保存,此乃真正的保存函数

async save(changed) {
    const opts = this.opts;
    const key = opts.key;
    const externalKey = this.externalKey;
    let json = this.session.toJSON();
    // set expire for check
    let maxAge = opts.maxAge ? opts.maxAge : ONE_DAY;
    if (maxAge === 'session') {
      // do not set _expire in json if maxAge is set to 'session'
      // also delete maxAge from options
      opts.maxAge = undefined;
      json._session = true;
    } else {
      // set expire for check
      json._expire = maxAge + Date.now();
      json._maxAge = maxAge;
    }

    // save to external store
    if (externalKey) {
      debug('save %j to external key %s', json, externalKey);
      if (typeof maxAge === 'number') {
        // ensure store expired after cookie
        maxAge += 10000;
      }
      await this.store.set(externalKey, json, maxAge, {
        changed,
        rolling: opts.rolling,
      });
      if (opts.externalKey) {
        opts.externalKey.set(this.ctx, externalKey);
      } else {
        this.ctx.cookies.set(key, externalKey, opts);
      }
      return;
    }

    // save to cookie
    debug('save %j to cookie', json);
    json = opts.encode(json);
    debug('save %s', json);

    this.ctx.cookies.set(key, json, opts);
  }

enter description here

可以看到这里将 _expiremaxAge 也就是 session 时效相关的两个字段保存到了 session 中。其中 _expire 用于下次访问服务器时判断 session 是否过期,_maxAge 用来保存过期时间。 然后通过 externalKey 判断是否使用外部储存,进入不同的保存流程。

总结

这里借用一下这篇文章使用的流程图

流程图

很好的展示了整个的逻辑流程。


Welcome to my Blog

[转载]原文链接:https://segmentfault.com/a/1190000020643234

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-77062.html

文章标题:一探 koa-session 源码

相关文章
jQuery的一些技巧大放送
1、关于页面元素的引用 通过jquery的$()引用元素包括通过id、class、元素名以及元素的层级关系及dom或者xpath条件等方法,且返回的对象为jquery对象(集合对象),不能直接调用dom定义的方法。 2、jQuery对象与d...
2015-11-11
JS生成一维码(条形码)功能示例
本文实例讲述了JS生成一维码(条形码)功能的方法。分享给大家供大家参考,具体如下: 1、js代码: (function() { if (!exports) var exports = window; var BARS = [212...
2017-03-01
js获取数组的最后一个元素
在js里面如何获取一个数组的最后一个元素呢?这里总结了两种方法,有需要的朋友可以看看。 (1)js内置pop方法 pop() 方法用于删除并返回数组的最后一个元素,注意这里在获取了数组的最后一个元素的同时也将原数组的最后一个元素给删除了。如...
2017-03-22
Webpack(含 4)配置详解——从 0 配置一套开发模板
前言 源代码 熟悉 webpack 与 webpack4 配置。 webpack4 相对于 3 的最主要的区别是所谓的零配置,但是为了满足我们的项目需求还是要自己进行配置,不过我们可以使用一些 webpack 的预设值。同时 webpack...
2018-05-02
使用iview的组件 Table 表格,有固定列,设置其中一个列适应屏幕大小
描述 在使用iview的组件Table表格时,有固定列,表格列宽不等。 在表格平铺时,不能自适应宽度。 问题 每个列有需要设置的宽度,有固定的列,很难调整某一列的宽度为刚刚好的。此时需要某一列自适应宽度。 解决 <template...
2018-09-28
JavaScript获取本周周一,周末及获取任意时间的周一周末功能示例
本文实例讲述了JS获取本周周一,周末及获取任意时间的周一周末功能。分享给大家供大家参考,具体如下: 项目需要获取本周及任意一天的周一及周末 需格式化,示例代码如下: <!DOCTYPE html PUBLIC "-&#x2F...
2017-03-17
使用AngularJS开发我们下一款Web应用的七个理由
在当下这个电子商务时代,每一家企业都热衷于通过网络拓展自身业务。而这也使Web开发人员市场呈现出前所未有的红火态势。根据最近发布的一份调查报告,全球网站总数已经超过8.76亿个,而且这一数字还在不断上升当中。市场上用于Web开发的平台亦多种...
2015-12-25
Dcloud,hbuilderX开发uni-app第一天踩坑记录
其实大部分坑在 uni-app在官网都有介绍 具体位置在 在 uni-app 中使用 Vue.js 模块 官方文档中总结了很多坑,但我只说一下我今天遇到的: 解决办法:从后台获取数据后始用js对数据进行处理, 例子: 始用过滤器时: &lt...
2018-08-27
整理AngularJS中的一些常用指令
AngularJS指令用于扩展HTML。这些都是先从ng- 前缀的特殊属性。我们将讨论以下指令: ng-app - 该指令启动一个AngularJS应用。 ng-init - 该指令初始化应用程序数据。 ng-model - 此指令定义的...
2017-03-24
回到顶部