You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
asyncinitFromExternal(){constctx=this.ctx;constopts=this.opts;letexternalKey;if(opts.externalKey){externalKey=opts.externalKey.get(ctx);}else{externalKey=ctx.cookies.get(opts.key,opts);}if(!externalKey){// create a new `externalKey`this.create();return;}constjson=awaitthis.store.get(externalKey,opts.maxAge,{rolling: opts.rolling});if(!this.valid(json,externalKey)){// create a new `externalKey`this.create();return;}// create with original `externalKey`this.create(json,externalKey);}
它判断了 store ,如果有 store ,就会设置上 externalKey ,或者生成一个随机id。
基本可以看出,是在 sotre 中存储一些信息,并且可以通过 externalKey 去用来获取。
由此基本得出推断,session 并不是服务器原生支持,而是由web服务程序自己创建管理。
存放在哪里呢?不一定要在服务器,可以像 koa-session 一样骚气地放在 cookie 中!
接着看最后一个 Session 类。
Session类
老规矩,先看构造函数:
constructor(sessionContext,obj){this._sessCtx=sessionContext;this._ctx=sessionContext.ctx;if(!obj){this.isNew=true;}else{for(constkinobj){// restore maxAge from storeif(k==='_maxAge')this._ctx.sessionOptions.maxAge=obj._maxAge;elseif(k==='_session')this._ctx.sessionOptions.maxAge='session';elsethis[k]=obj[k];}}}
asyncsave(changed){constopts=this.opts;constkey=opts.key;constexternalKey=this.externalKey;letjson=this.session.toJSON();// save to external storeif(externalKey){awaitthis.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;}json=opts.encode(json);this.ctx.cookies.set(key,json,opts);}
前言
然而:
本文将从
koa-session(koa官方维护的session中间件)
的源码详细解读 session 的机制原理。希望大家读完后,会对 session 的本质,以及 session 和 cookie 的区别有个更清晰的认识。基础知识
相信大家都知道一些关于 cookie 和 session 的概念,最通常的解释是 cookie 存于浏览器,session 存于服务器。
cookie 是由浏览器支持,并且http请求会在请求头中携带 cookie 给服务器。也就是说,浏览器每次访问页面,服务器都能获取到这次访问者的 cookie 。
但对于 session 存在服务器哪里,以及服务器是通过什么对应到本次访问者的 session ,其实问过一些后端同学,解释得也都比较模糊。因为一般都是服务框架自带就有这功能,都是直接用。背后的原理是什么,并不一定会去关注。
如果我们使用过koa框架,就知道koa自身是无法使用 session 的,这就似乎说明了 session 并不是服务器原生支持,必须由 koa-session 中间件去支持实现。
那它到底是怎么个实现机制呢,接下来我们就进入源码解读。
源码解读
koa-session:https://github.com/koajs/session
koa-session结构
来看 koa-session 的目录结构,非常简单;主要逻辑集中在 context.js 。
先给出一个 koa-session 主要模块的脑图,可以先看个大概:
屡一下流程
我们从 koa-session 的初始化,来一步步看下它的执行流程:
先看下 koa-sessin 的使用方法:
初始化
初始化 koa-session 时,会要求传入一个app实例。
实际上,正是在初始化的时候,往 app.context 上挂载了session对象,并且 session 对象是由
lib/context.js
实例化而来,所以我们使用的 ctx.session 就是 koa-session 自己构造的一个类。我们打开
koa-session/index.js
:通过内部的一次初始化,返回一个koa中间件函数。
一步一步的来看,formatOpts 是用来做一些默认参数处理,extendContext 的主要任务是对 ctx 做一个拦截器,如下:
走到上面这段代码时,事实上就是给 app.context 下挂载了一个“私有”的 ContextSession 对象
ctx[CONTEXT_SESSION]
,有一些方法用来初始化它(如initFromExternal、initFromCookie)。然后又挂载了一个“公共”的 session 对象。为什么说到“私有”、“公共”呢,这里比较细节。用到了 Symbol 类型,使得外部不可访问到
ctx[CONTEXT_SESSION]
。只通过ctx.session
对外暴露了 (get/set) 方法。再来看下
index.js
导出的中间件函数这里,将
ctx[CONTEXT_SESSION]
实例赋值给了 sess ,然后根据是否有 opts.store ,调用了sess.initFromExternal
,字面意思是每次经过中间件,都会去调一个外部的东西来初始化 session ,我们后面会提到。接着看是执行了如下代码,也即执行我们的业务逻辑。
然后就是下面这个了,看样子应该是类似保存 session 的操作。
经过上面的代码分析,我们看到了
koa-session
中间件的主流程以及保存操作。那么 session 在什么时候被创建呢?回到上面提到的拦截器
extendContext
,它会在接到http请求的时候,从ContextSession类
实例化出 session 对象。我们接着看核心功能
ContextSession
。ContextSession类
先看构造函数:
居然啥屁事都没干。往下看
get()
方法:噢,原来是一个单例模式(等到使用时候再生成对象,多次调用会直接使用第一次的对象)。
这里有个判断,是否传入了 opts.store 参数,如果没有则是用
initFromCookie()
来生成 session 对象。那如果传了 opts.store 呢,又啥都不干吗,WTF?
显然不是,还记得初始化里提到的那句
initFromExternal
函数调用么。所以,这里是根据是否有 opts.store ,来选择两种方式不同的生成 session 方式。
initFromCookie
在这里,我们发现了一个很重要的信息,session 居然是加密后直接存在 cookie 中的。
我们
console.log
一下json
变量,来验证下:initFromeExternal
可以看到
store.get()
,有一串信息是存在 store 中,可以 get 到的。而且也是在不断地要求调用
create()
。create
create()
到底做了什么呢?它判断了 store ,如果有 store ,就会设置上
externalKey
,或者生成一个随机id。基本可以看出,是在 sotre 中存储一些信息,并且可以通过 externalKey 去用来获取。
接着看最后一个 Session 类。
Session类
老规矩,先看构造函数:
接收了 ContextSession 实例传来 sessionContext 和 obj ,其他没有做什么。
Session 类仅仅是用于存储 session 的值,以及_maxAge,并且提供了toJSON方法用来获取过滤了_maxAge等字段的,session对象的值。
session如何持久化保存
看完以上代码,我们大致知道了 session 可以从外部或者 cookie 中取值,那它是如何保存的呢,我们回到
koa-session/index.js
中提到的commit
方法,可以看到:思路立马就清晰了,它是在中间件结束
next()
后,进行了一次commit()
。commit()
方法,可以在lib/context.js
中找到:再来看
save()
方法:豁然开朗了,实际就是默认把数据 json ,塞进了 cookie ,即 cookie 来存储加密后的 session 信息。
然后,如果设置了外部 store ,会调用
store.set()
去保存 session 。具体的保存逻辑,保存到哪里,由 store 对象自己决定!小结
koa-session 的做法说明了,session 仅仅是一个对象信息,可以存到 cookie ,也可以存到任何地方(如内存,数据库)。存到哪,可以开发者自己决定,只要实现一个 store 对象,提供 set,get 方法即可。
延伸扩展
通过以上源码分析,我们已经得到了我们文章开头那些疑问的答案。
koa-session 中还有哪些值得我们思考呢?
插件设计
不得不说,store 的插件式设计非常优秀。koa-session 不必关心数据具体是如何存储的,只要插件提供它所需的存取方法。
这种插件式架构,反转了模块间的依赖关系,使得 koa-session 非常容易扩展。
koa-session对安全的考虑
这种默认把用户信息存储在 cookie 中的方式,始终是不安全的。
所以,现在我们知道使用的时候要做一些其他措施了。比如实现自己的 store ,把 session 存到 redis 等。
这种session的登录方式,和token有什么区别呢
这其实要从 token 的使用方式来说了,用途会更灵活,这里就先不多说了。
后面会写一下各种登录策略的原理和比较,有兴趣的同学可以关注我一下。
总结
回顾下文章开头的几个问题,我们已经有了明确的答案。
那么除了 koa-session 这种方式来实现用户登录,还有其他方法吗?
其实还有很多,可以存储 cookie 实现,也可以用 token 方式。另外关于登录还有单点登录,第三方登录等。如果大家有兴趣,可以在后面的文章继续给大家剖析。
The text was updated successfully, but these errors were encountered: