brunoyang / blog

134 stars 13 forks source link

cookie & session & koa #16

Open brunoyang opened 8 years ago

brunoyang commented 8 years ago

产品经理之死

http是无状态的,你不能通过观察一条http请求来猜想前一条或后一条请求可能的样子。这为网站管理登录用户带来了困难,因为无状态,所以你无法知道这条请求从何而来。在1994年时,网景浏览器弄出了可以存储在用户本地的一小段文本信息,被称为cookie。

这个名字还有一段小故事,美国有一款叫Fortune cookie的饼干,里面藏有一张写着有趣句子的纸条。网景借鉴了这个寓意,把http内的隐藏信息称为cookie。

用户在访问某个网站时,服务器的响应头里会带有Set-Cookie字段,告诉浏览器存储这里面的信息,于是用户的浏览器里就带有cookie了,发第二条请求时就会带上Set-Cookie字段里的值,这就是客户端能做的所有。

这时来了个产品经理,说我们网站要支持登录。所谓登录,就是用户带上凭证,server允许其进行不带凭证的用户不能进行的操作。用户登录后,server在response里加上Set-Cookie字段,并在server里(内存/文件/数据库)存储该值。下一次用户再发来请求,就会带上cookie,然后server取出cookie与存储内的值作对比,若匹配,我们就会认为改用户是已登录的,从而放行操作。以上的行为,就可以称为维持了一段会话(session)。

这时产品经理说,我们网站要支持20分钟超时登出。也就是说如果用户20分钟内没有发来任何请求,我们就认为用户死了,也就没有维持这段会话的必要了,可以删除掉存储的cookie值了。但在未过期之前,用户每发来一个请求,就给该用户 +20m。

永不满足的产品经理说,我们网站要提高安全性,不管你们干嘛,就是要提高安全性。经过一番安全检查,我们发现我们的cookie有被篡改的风险。针对这种情况,我们可以给cookie加签。我们选择把cookie增加个时间戳字段,然后进行加密后提取摘要,再把这个摘要值放在cookie和server的存储中。在下一次请求时,我们就会对比这两个值是否相等,若相等,重新更新时间戳并计算,保证每条请求都是独一无二的;若不相等,说明该cookie被篡改。

后来,产品经理说,我们要支持单点登录。后来他坟头草两丈高了吧。

koa-generic-session

上面说了这么多,我们再结合代码来看看,下面是根据 koa-generic-session@2.10.2做的源码解读。

来看/lib/session.js。

const session = require('koa-generic-session');
const app = require('koa')();

app.use(session());

这是最基本的用法,session可以传入配置项,如下:

{
  key, // session id的键名
  store, 
  // 如何存储session,一般会选择redis。
  // 若不传入,会默认存储在内存中,在生产环境下会产生一个警告
  ttl, // session过期时间
  prefix, // 存储sessoin时的前缀
  cookie, // cookie配置项
  defer, 
  // 默认情况下,每次app.use(session());后都会去生成存储session,但静态文件是不需要session的。
  // 所该选项为true,调用yield this.session;才会去生成session。
  genSid, // 可以自定义生成session的方法
  errorHanlder, // 自定义处理session存储和取出失败时的错误处理
  valid, // 自定义校验session,
  beforeSave, // 钩子函数
  sessionIdStore, // object,内部需要有set,get,reset三个方法用来往ctx.cookies上设置cookie
}

选项中有个defer字段,作用如下

return options.defer ? deferSession : session;

在设置defer为true后只有在调用yield this.session时才会生成session,节省资源。我们来看deferSession函数。

代码太长,只挑要紧的讲。在这个函数内有getter/setter,和regenerateSession方法。getter方法取得session,若已被setter方法设置过,就直接返回设置的session。若没设置过,会调用getSession获取session。

getSession的流程就是先检查sessionId(cookie中),若没有说明是新连接,调用generateSession生成session,若不是,则从存储中取出的对应的session(可能为空,因为访问间隔超过超时时间),随后返回一个object。

在做完上面的步骤后,就会调用refreshSession,当session变为空,就从存储中删掉该条session,若发生改变,则更新cookie和存储中的值。

session方法就是deferSession的简化版,就不重复了。

icepy commented 8 years ago

koa-generic-session 和 koa-csrf 配合是发生 令牌丢失错误,请问下这个从哪方面开始着手查看这个错误呢。

brunoyang commented 8 years ago

能贴下具体的报错信息吗