Node 中 Cookie、Session 与 Redis 缓存

2,253 阅读7分钟

HTTP 协议和 TCP/IP 协议组中其它协议相同,用于客户端和服务器端之间的通信,HTTP是一种无状态协议,协议本身不保存客户端和服务端的通信状态,也就是说在 HTTP 这个级别不会对请求或响应做持久化处理,当然这也是为了更快的处理大量事务,确保协议的可伸缩性。

一、cookie

1.1 概述

在 HTTP 协议中,制定了 Cookie 机制,用于实现客户端和服务器之间的状态共享。

Cookie 是解决 HTTP 无状态性的有效手段,服务器可以设置或读取 cookie 中所包含的信息。常见的情景是:

  • 当用户登录后,服务器会发送包含登录凭据的 cookie 到用户浏览器客户端,而浏览器对该 cookie 进行某种形式的存储(内存或硬盘)。
  • 用户再次访问该网站时,浏览器会发送该 cookie(cookie 未到期时)到服务器,服务器对该凭据进行验证,合法时使用户不必输入用户名和密码就可以直接登录。

为什么浏览器会这样操作?

当网页要发 http 请求时,浏览器会先检查是否有相应的 cookie,有则自动添加在 request header 中的Cookie字段中。这是浏览器自动做的,而且每一次 http 请求浏览器都会这样做。这个特点很重要,因为这关系到什么样的数据适合存储在 cookie 中。如果数据并不是每个请求都需要发给服务端的数据,浏览器这样处理无疑增加了网络开销;而如果数据是每个请求都需要发给服务端的(比如身份认证信息),浏览器这样自动处理就大大免去了重复添加操作。所以对于设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。

但在 localStorage 出现之前,cookie 被滥用当做了存储工具。什么数据都放在 cookie 中,即使这些数据只在页面中使用而不需要随请求传送到服务端。当然 cookie 标准还是做了一些限制的:每个域名下的 cookie 的大小最大为4KB,每个域名下的 cookie 数量最多为20个(但很多浏览器厂商在具体实现时支持大于20个)。

img

1.3 cookie 类别

cookie 总是存储在客户端,通常可以按 expires 到期时间分为两类:内存式cookie、硬盘式cookie

内存式:存储在内存中,浏览器关闭后清除,也叫非持久存储(会话 cookie)。cookie 不包含到期日期,则可视为会话 cookie。 会话 cookie 存储在内存中,当浏览器关闭时,cookie 将永久丢失。

硬盘式:保存在硬盘中,浏览器关闭后不会清除,除非手动清除或到了过期时间,也叫持久存储(持久 cookie)。cookie 包含到期日期,则可视为持久性 cookie。 在指定的到期日期,cookie 将从磁盘中删除。

1.4 HTTP 协议中为 cookie 服务的首部字段

Set-cookie: response headers 字段。属性之间用分号和空格隔开。cookie的值字符串可以用encodeURIComponent()来保证它不包含任何逗号、分号或空格( cookie 值中禁止使用这些值).

Cookie: request header 字段,服务端接收到的cookie信息。

img

属性 说明
{KEY}={VALUE} cookie key/value (必须)(*可以多个吗?*可以,需要写多个 set-cookie语句)
expires={DATE} 指定浏览器可以发送 cookie 的有效期(默认为session,即会话 cookie,浏览器关闭为止)
domain={DOMAIN} 指定 cookie 可以送达的主机名。(默认值为当前文档访问地址中的主机部分(但是不包含子域名))
path={PATH} 指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部。字符%x2F ("/") 可以解释为文件目录分隔符,此目录的下级目录也满足匹配的条件(例如,如果 path=/docs,那么 "/docs", "/docs/Web/" 或者 "/docs/Web/HTTP" 都满足匹配的条件)。(默认为当前文档的路径)
secure 仅在 https 通信时才会发送 cookie
HttpOnly 禁止 cookie 被 js 脚本访问(js 获取 cookie 的方法是document.cookie),防止跨站监本攻击对 cookie 信息窃取

expires是 http/1.0协议中的选项,在 http/1.1 协议中expires已经由max-age 选项代替,两者的作用都是限制 cookie 的有效时间。expires的值是一个时间点(cookie失效时刻),而max-age 的值是一个以秒为单位时间段。如果两者都存在,max-age优先级更高。

另外,max-age的默认值是 -1(即有效期为 session );max-age有三种可能值:负数、0、正数。负数表示有效期为 session;0表示删除cookie;正数表示有效期为创建时刻 + max-age

1.5 中间件

cookie-parser (看文档学习如何与 Express 或者 Koa 结合使用)

也可以参考这里:chenshenhai.github.io/koa2-note/n…

二、session

session 需要借助 cookie 实现,session 数据存储在服务端,而只在 cookie 中存储一个 sessionId,可以保证安全性和降低服务器负载。

可以查询中间件 express-session 或者 koa-session 的文档。

也可以参考这里:chenshenhai.github.io/koa2-note/n…

三、Redis

Redis 是一个开源的、使用 ASCI C 编写的、可基于内存及持久化的日志型 key-value 数据库,提供多种语言 API。可用作数据库、高速缓存和消息队列代理,非常适合于短时间内高频访问但又不需要长期访问的简单数据存储。

session存在的问题:session 用于在服务端保存用户会话状态(如:用户登录信息等) ,session 在程序重启、多进程运行、负载均衡、跨域等情况时,会出现丢失或多进程、多个负载站点间状态不能共享的情况。

要解决这些问题:我们需要将 session 持久化存储,Redis 存储是一个非常不错的 session 持久化解决方案。

Redis 是一个高性能的 key-value 数据库(与 SQL 数据库的关系是什么?如何存储?

3.1 概述

特点

  • Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供listsetzset(有序集合)、hash等数据结构的存储
  • Redis支持数据的备份,即 master-slave 模式的数据备份(?)

优势

  • 性能极高 – Redis 能读的速度是110000次/s,写的速度是81000次/s(什么版本?什么硬件?来源?
  • 丰富的数据类型 – Redis 支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作
  • 原子 – Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子性执行
  • 丰富的特性 – Redis还支持 publish/subscribe,通知,key 过期等等特性

3.2 中间件

node-redis connect_redis

示例:

const Koa = require('koa');
const app = new Koa();
const session = require('koa-session');
const redis = require('redis'); //引入 node redis 库
const client = redis.createClient(6379, '127.0.0.1'); //连接本地Redis服务
const { promisify } = require('util');
//用 promisify改造 client.hgetall
const hgetallAsync = promisify(client. hgetall).bind(client);
app.keys= ['some secret hurr'];
const store = { //配置 Redis 如何存取 Session 
    get: async (key, maxAge) => await hgetallAsync(key), //从Redis获取Session
    set: (key, sess, maxAge) => client.hmset(key, session),
    destroy: key => client.hdel(key),
};
const config = {
    key: 'koa:sess',
    maxAge: 86400000,
    overwrite: true,
    httpOnly: true,
    signed: true,
    store,
};
app.use(session(config, app));
app.use(ctx => {
    if (ctx.path === '/favicon.ico') return;
    let n = ctx.session.views || 0;
    ctx.session.views = ++n;
    ctx.body = n + `views`
})
app.listen(3000);

参考

  1. node中Session持久化与Redis缓存
  2. 聊一聊 cookie
  3. 权限处理 - 用redis实现分布式session~ (cookie && session )
  4. developer.mozilla.org/zh-CN/docs/…