Session是什么
session机制采用的是一种在服务器端保持状态的解决方案。由于采用服务器端能保持状态。也需要客户端保存一个标识。所以session机制可能需要借助于cookie机制来达到保存标识的目的。
- 服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串。
- 签名。这一步通过秘钥对sid进行签名处理,避免客户端修改sid。(非必需步骤)
- 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息,
- 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
如果按照经典实现的方式,session数据是保存在服务器端的,那么假设session不使用外部存储设备,session也就是存储在内存中的。那么如果一旦服务器重新启动所有session就会消失。
诡异的Koa-session
好那么我们看看Koa-Session是不是可以复现这个问题。
const koa = require('koa') const app = new koa() const session = require('koa-session') // 签名key keys作用 用来对cookie进行签名 app.keys = ['some secret']; // 配置项 const SESS_CONFIG = { key: 'kkb:sess', // cookie键名 maxAge: 86400000, // 有效期,默认一天 httpOnly: true, // 仅服务器修改 signed: true, // 签名cookie }; // 注册 app.use(session(SESS_CONFIG, app)); // 测试 app.use(ctx => { if (ctx.path === '/favicon.ico') return; // 获取 let n = ctx.session.count || 0; // 设置 ctx.session.count = ++n; ctx.body = '第' + n + '次访问'; }); app.listen(3000)
按照程序功能变量count每次都会累加,如果重新启动服务。count应该归零。但是结果确不是这样。
原因分析
这时候我们细致的分析一下。首先我么发现在这里存储sid的cookie字段的值是eyXXXXX这样的一个值。说明很可能是base64编码的json。不是我们通常认为的uuid。
通过在线的base64解码我们确认了这个结果。
这样问题就很清晰了。原来Koa-session默认确实是通过将session序列化后存储在客户端的。 我们从以下的npm文档中也可以证实了这个说法。
文档里明确声明koa默认存储是放在客户端的非加密数据。
客户端存储的优缺点分析
优点
首先先说优点。客户端存储的优点大概有两个。
- 低成本的实现了session的持久化问题
- 低成本的实现了多实例共享session问题
通常情况下我们如果实现session的持久化和全局共享都需要使用第三方存储比如redis或者mongodb。client端序列化方式就很好的解决了这个问题。但是使用客户端存储就很好的解决了这个问题。
缺点
缺点也有两条
- 存储容量有限制(Cookie 大小一般是4k)
- 安全性问题由于存储在客户端而且是非加密数据所以不能存储敏感数据。
如何指定存储方式
在Koa-Session中如果希望变更存储方式可以通过实现store接口的方式实现。
内存实现
首先我们先写一个基础的内存实现。其实就是为了熟悉一下这个接口。
class Memory { constructor () { this.sotry = {} } get(key) { console.log('get:',key) return this.sotry[key] } set(key,sess) { console.log('set:',key,sess) this.sotry[key] = sess } destroy (key) { console.log('destroy:',key) this.sotry = {} } } const SESS_CONFIG = { key: 'kkb:sess', store: new Memory() // 指定实现 } app.use(session(SESS_CONFIG, app))
可以看到指定了存储后,koa-session会调用存储接口中的getter和setter方法。
浏览器中也不会存储序列化数据而是只存储sid也就是uuid。
完整代码
Redis实现
另外我们再看看生产环境中常用的Redis存储方式。
const redisStore = require('koa-redis') const redis = require('redis') const redisClient = redis.createClient(6379, 'localhost') const wrapper = require('co-redis') const client = wrapper(redisClient) app.keys = ['some secret'] const SESS_CONFIG = { key: 'kkb:sess', // 名 store: redisStore({ client }) } app.use(session(SESS_CONFIG, app))
总结
Koa的默认存储方式选择的是客户端序列化方式,刷新了我们对Session常规的印象。这种方式其实借鉴了JWT Token的实现方法。非常适合小型应用。可以很低成本的解决持久化和横向扩展问题。