使用 koa-session 时,获取 sessionid。

tl;dr

先说结论,如果在使用 koa-session 时需要获取当前新建 sessionsessionid 时,可以通过手动调用 koa-sessionsave 方法立即保存当前的 session 更改,即 await ctx.session.manuallyCommit();await ctx.session.save();(强制保存),调用后,我们可以在 ctx.response.headerset-cookie 属性中拿到当前的 sessionid

接下来说下场景和原因。

场景

项目技术背景如下,其中单点的逻辑这里简化描述,我们目前使用的方案是 CAS,下文我们也会用 CAS 表述单点服务。

使用了 koas-session 管理 session
使用 redis 存储 session(这个可以在 session 方法的 opitons 属性中配置)
接入了 CAS,登录成功后,我会把 CAS 下发的 ticket 和当前 session 的 sessionid,存入 redis(退出登录时会使用到)
在服务里给 CAS 预留一个登出入口,CAS 登出时会请求这个接口,我们可以拿到 CAS 带来的 ticket 参数,通过 ticket,我们可以从 redis 里拿到对应的 sessionid,再通过 sessionid 销毁对应的 session
注:用户在接入 CAS 的项目中登出时,CAS 会对所有接入的端发送登出的请求,各个端系统在预留的登出接口里配置 session 销毁逻辑

这个方案没有问题,唯一的问题在于实现时发现,koa-session 并没有暴露当前 sessionid 的方法,如果拿不到 sessionid,整个流程都会被卡主,获取方法前面已经提过了,下面我们聊聊为什么。

原因

看过 koa-session 源码的同学应该知道,当使用外部存储时(mongo、redis 等)时,以 redis 为例,这个中间件会将 session 数据存在 redis 里,将 sessionid 写入客户端的 cookie,当客户端再次请求时,通过读取 cookie,再从 redis 中拿出 session。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 保存到外部存储
if (externalKey) {
debug('save %j to external key %s', json, externalKey);
if (typeof maxAge === 'number') {
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;
}

当看到 this.ctx.cookies.set(key, externalKey, opts); 这行代码时,意识到可以通过拿 set-cookie 来获取,但是,在配置 session 后,打印 response.header 时却没看到任何 set-cookie 的信息,再看源码,上面保存的方法被放置到了最后触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = function(opts, app) {
if (opts && typeof opts.use === 'function') {
[ app, opts ] = [ opts, app ];
}
if (!app || typeof app.use !== 'function') {
throw new TypeError('app instance required: `session(opts, app)`');
}
opts = formatOpts(opts);
extendContext(app.context, opts);
return async function session(ctx, next) {
const sess = ctx[CONTEXT_SESSION];
if (sess.store) await sess.initFromExternal();
try {
await next();
} catch (err) {
throw err;
} finally {
if (opts.autoCommit) {
// commit 方法中执行上文的保存操作
await sess.commit();
}
}
};
};

这样就太被动了,不过还好暴露除了 savemanuallyCommit 两种方式触发保存,区别是 save 是冒着 session 被污染的风险强制更新,保存之后,再次打印,令人满意的 set-cookie 如约出现。

多一种方案

获取 koa-session 默认生成的 sessionid,只是一种方案,我们还可以生成自定义 sessionid,配置到 option 中的 externalKey 属性,可以达到制定 sessionid 的目的。