Express.js 中 cookie 和 session 的使用

2019-09-13

HTTP 协议是无状态的,即服务器无法识别两条 http 请求是否由同一客户端发起,cookie 和 session 就是用来记录状态、确定请求的客户端身份。

概念

cookie 由服务端发起,对不同的用户发送不同的标识,客户端在之后的请求头中加入这个标识,服务端就可以通过这个标识来识别用户的身份,步骤如下:

  • 服务端向客户端发送 cookie
    • 使用 HTTP 协议规定的 set-cookie 头操作
    • 规范规定的 cookie 格式为 name=value
  • 浏览器保存 cookie
  • 之后的请求中携带该 cookie

在项目中使用

cookie-parser 中间件方便了对 cookie 的解析,可以将 cookie 解析为 {name: value} 格式的对象

yarn add cookie-parser

引入项目

const cookieParser = require('cookie-parser');

app.use(cookieParser());

app.get('/', (req, res) => {
  if (req.cookies.myCookie) {
    console.log(req.cookies.myCookie); // 读取 cookie
    res.end();
  } else {
    res.cookie('myCookie', 'foobar', {maxAge: 1000 * 60}); // 存储 cookie,1分钟
    res.end();
  }
})

cookieParser(secret, options) 有两个参数,第一个用来对 cookie 签名,可以是字符串或数组,如果是数组,解密时会一一尝试数组中的每一项作为加密字符串;第二个 options 是可选参数,包括:

  • path: 指定 cookie 影响的路径
  • expires: cookie 失效时间,UTC 时间格式
  • maxAge: cookie 多久后失效,单位毫秒,不设置 expires 和 maxAge 时,会产生 session cookie, session cookie 在关闭浏览器时会被清除,一般用来保存 session 的 session_id
  • secure: 当 secure 为 true 时,在 HTTPS 中有效,在 HTTP 中无效;否则在 HTTP 中有效,HTTPS 中无效
  • httpOnly: 为 true 时,浏览器不允许脚本操作 document.cookie,避免 XSS 攻击拿到 cookie
  • domain: 指定域名

signedCookies

对 cookie 进行签名,这样请求头里的 cookie 数据是加密后的,在服务端 cookie-parser 会自动解密

const cookieParser = require('cookie-parser');

app.use(cookieParser('my_cookie_secret')); // 设置签名字符串

app.get('/', (req, res) => {
  if (req.signedCookies.myCookie) {
    console.log(req.signedCookies.myCookie); // 读取 signedCookies,已解密
    res.end();
  } else {
    // 存储 cookie,设置 signed 为 true 来加密 cookie
    res.cookie('myCookie', 'foobar', {maxAge: 1000 * 60, signed: true});
    res.end();
  }
})

session

cookie 的使用很方便,但是有弊端:cookie 中的数据在客户端可以被修改和伪造,而且 cookie 中的数据字段太多会影响传输效率。为了解决这些问题,就产生了 session,session 中的数据存储在服务端

session 的运作通过 session_id 来进行,session_id 通常存放在客户端的 cookie 中,在 express 中默认是 connect.sid 字段,服务端会检查请求头中 cookie 所保存的 session_id 并与服务端的 session data 关联,进行数据的操作

也就是说,服务端会产生一个 1024 比特长的字符串,存储在 cookie 的 connect.sid 中,之后的请求会带有这个字符串,服务端就可以从 redis 等存储中拿到对应身份的数据

session 可以存放的位置有内存、cookie、redis、memecached 或数据库,通常会存储在 redis 和 memcached 缓存中

在 express 中使用 session 需要 express-session 中间价

yarn add express-session

在项目中使用

const session = require('express-session');

app.use(session({
  secret: 'my_session_secret',
  cookie: {maxAge: 1000 * 60}
}));

app.get('/', (req, res) => {
  if (req.session.mySession) {
    console.log(req.session.mySession); // 读取 session
    res.end();
  } else {
    req.session.mySession = 'foobar'; // 写入 session
    res.end();
  }
})

session(options) 参数

  • name: 设置 cookie 中保存 session 的字段名称,默认为 connect.sid
  • store: session 的存储方式,默认存放在内存中,也可以存放在 redis 等缓存中
  • secret: 签名字符串,用来计算 hash 值并存放在 cookie 中
  • cookie: 设置存放 session_id 的 cookie 的相关选项,默认为
    • {path: '/', httpOnly: true, secure: false, maxAge: null}
  • genid: 产生一个新的 session_id 时使用的函数,默认使用 uid2 这个 npm 包
  • rolling: 每个请求都重新设置一个 cookie,默认 false
  • resave: 即使 session 没有被修改,也保存 session 值,默认 true
  • saveUninitialized: 强制未初始化的session保存到数据库

cookie session 就是将 session 存储在 cookie 中,比如存储用户数据

const sessionData = {username: 'foo'};

对该数据与 secret 字符串做加密,将加密后的字符串存储到 cookie 的 session_data 字段中,之后的请求解密 session_data 即可

redis 中存储 session

在 redis 中存储 session,需要 redisconnect-redis

yarn add redis connect-redis

在项目中使用

const redis = require('redis');
const session = require('session');

let RedisStore = require('connect-redis')(session);
let redisClient = redis.createClient({
  host: '127.0.0.1',
  port: 6379,
  db: 0,
  password: ''
});

app.use(session({
  secret: 'my_session_secret',
  resave: false,
  saveUnintialized: false,
  store: new RedisStore({client: redisClient})
}))
NodeJS

Jeff Liu

vultr + v2ray 实现科学上网及 websocket + tls 流量伪装

Gin框架中JWT的使用初探