「翻译」express-session 中间件

4,255 阅读12分钟

前言:最近在使用 express-session 中间件,查看使用的时候有些参数不是很清楚就花了一点时间把文档翻译了一下。

其中常用设置中令人迷糊的是 resave 和 saveUnintialized 属性,关于这两个属性,引用来自 CNODE 社区更通熟易懂的解释

resave : 是指每次请求都重新设置 session cookie,假设你的 cookie 是 10 分钟过期,每次请求都会再设置 10 分钟。
saveUninitialized: 是指无论有没有 session cookie ,每次请求都设置个 session cookie ,默认给个标示为 connect.sid。

以下为正文

Installation

这是一个通过 npm registry 可用的 Node.js 模块。使用以下 npm install 命令来完成安装。

$ npm install express-session

API

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

sessions(options)

使用给定选项创建一个 session 中间件。

注意:只有 session ID 是保存在 cookie 中,Session 数据本身并不是。Session 数据是存在服务端。

注意:从版本 1.5.0 起,本模块不再需要 cookie-parser 中间件来运行。本模块现在直接在 req/res 上读写 cookies。当本模块和 cookie-parser 的 secret 不一致时,使用 cookie-parser 可能会导致问题。

警告:默认的服务端 session 存储,MemoryStore,特意没有为生产环境而设计。在大多数情况下,它可能会导致内存泄漏,不会扩展超过单个进程,本是用于调试和开发。

对于存储列表,请查看兼容的 session 存储

Options

express-session 在 options 对象中接收以下参数

cookie

session ID cookie 的设置对象。默认值为 { path: '/', httpOnly: true, secure: false, maxAge: null }.

下列参数可选设置放入 cookie 对象。

cookie.domain

为 Set-Cookie 属性指定 domain。默认情况下,没有设置 domain,并且大多数客户端会将 cookie 视为仅应用于当前 domain。

cookie.expires

为 Set-Cookie 属性中的 Expires 指定 Date 对象。默认情况下,没有设置 expires,大多数客户端会将视这个为 "非持久化 cookie" 并且在像退出浏览器应用的场景下删除该 cookie。

注意:如果 options 对象中同时设置了 expires 和 maxAge,那么将被用到的是在对象中最后一个被定义的属性。

注意:expires 选项不应该被直接设置;而应该只使用 maxAge 选项。

cookie.httpOnly

为 Set-Cookie 属性中的 HttpOnly 指定 boolean 值。当为真值,HttpOnly 属性被设置,否则不被设置。默认情况下,HttpOnly 属性是被设置的。

注意:设置该值为 true 的时候要小心,因为服从协议的客户端不会允许 JavaScript 在 document.cookie 中查看 cookie。

cookie.maxAge

指定当计算 Set-Cookie 属性中的 Expires 时使用的 number (毫秒)值。这是通过获取当前服务器时间并将 maxAge 毫秒数加入其中计算 Expires 日期时间来完成的。默认情况下没有设置 maxAge。

注意:如果 options 对象中同时设置了 expires 和 maxAge,那么将被用到的是在对象中最后一个被定义的属性。

cookie.path

为 Set-Cookie 属性指定 Path 值。默认情况下该值被设为 '/',也就是 domain 下的根路径。

cookie.sameSite

为 Set-Cookie 属性中的 SameSite 指定 boolean 或者 string 值。其中,

  • true 会将 SameSite 属性设为 Strict 以实现严格的相同站点强制。
  • false 不会 SameSite 属性。
  • 'lax' 会将 SameSite 属性设置为 Lax 以实现宽松的相同站点强制。
  • 'strict' 会将 SameSite 属性设置为 Strict 以实现严格的相同站点强制。

关于不同的强制级别的更多信息可以在细则中找到tools.ietf.org/html/draft-…

注意:这是一个还未被完全标准化的属性并且将来可能发生变化。这意味着许多客户端可能忽略这条属性直到它们完全理解它为止。

cookie.secure

为 Set-Cookie 属性中的 Secure 指定 boolean 值。当为真时,Secure 属性被设置否则没有设置。默认情况下 Secure 属性没有被设置。

注意:当设置该值为 true 的时候请小心,因为如果浏览器没有建立 HTTPS 连接服从协议的客户端将不会发送 cookie 返回给服务端。

请注意 secure: true 是推荐选项。然而,它需要启用 HTTPS 的网站, 也就是 HTTPS 是 secure cookies 所必须的。如果 secure 被设置而你通过 HTTP 访问你的站点,cookie 将不会被设置。如果你在代理后使用 node.js 并且设置 secure: true,你需要在 express 中设置 "trust proxy":

var app = express()
app.set('trust proxy', 1) // trust first proxy
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true }
}))

为了在生产环境中使用 secure cookies,同时允许在开发环境中测试,下列是在 express 中基于 NODE_ENV 启用此设置的示例:

var app = express()
var sess = {
  secret: 'keyboard cat',
  cookie: {}
}
 
if (app.get('env') === 'production') {
  app.set('trust proxy', 1) // trust first proxy
  sess.cookie.secure = true // serve secure cookies
}
 
app.use(session(sess))

cookie.secure 选项也可以被设置成特殊值 "auto" 来让这个设置自动和确定的连接的安全性相匹配。如果站点可以同时用做 HTTP 和 HTTPS 请小心使用这个设置,因为一旦 cookie 的 HTTPS 属性被设置,cookie 不会再对 HTTP 可见。当 Express 的 "trust proxy" 被正确设置来简化开发和生产配置的时候,这非常有用。

genid

调用来生成一个新的 session ID 的函数。提供一个返回 string 类型并将被用来作为 session ID 的函数。当生成 ID 的时候如果你想用一些附加到 req 的值,该函数已给定 req 作为第一个参数。

默认值是一个使用 uid-safe 库来生成 ID 的函数。

注意:请小心生成唯一的 ID 以便你的 sessions 不会产生冲突。

app.use(session({
  genid: function(req) {
    return genuuid() // use UUIDs for session IDs
  },
  secret: 'keyboard cat'
}))
name

设置在 response 中(和从 request 中读取)的 session ID 的 cookie 的 name。

默认值为 "connect.sid"

注意:如果你有多个运行在相同 hostname(只是名字,也就是 localhost 或者 127.0.0.1;不同的协议(scheme) 和 端口(port) 不命名不同的主机名)上的应用,那么你需要将 session cookie 彼此分开。最简单的方法是每个应用设置不同的 name。

proxy

当设置 secure cookies 的时候相信反向代理(通过 "X-Forwarded-Proto" 头)。

默认值为 undefined

  • true 表示 "X-Forwarded-Proto" 头将会被使用。
  • false 表示只有存在直接的 TLS/SSL 连接时才会忽略所有头并认为连接是安全的。
  • undefined 表示从 express 中使用 "trust proxy"。
resave

即使 session 在请求期间从未被修改过也强制 session 保存回 session 存储(store)。根据你的存储这可能是必须的,但是这也可能创造竞争条件当客户端发送两个并行请求到你的服务端并且其中一个请求 A 对 session 作出的更改可能会在另一个请求 B 结束时被覆盖即使请求 B 没有做任何更改(这个行为取决于你用的 session 存储)。

默认值为 true,但是不推荐使用默认值,因为默认值将来会被更改。请研究此项设置并选择适合你的用例的选项。一般来讲,你会想选择 false。

该如何知道该设置对我的 session 存储来讲是不是必须的呢?最好的方法是检查你的存储看它是否实现了 touch 方法。如果它实现了,那你可以安全地设置 resave 为 false。如果它没有实现 touch 方法而且你的 store 在存储的 sessions 中设定的 expiration 日期,那么你可能需要设置 resave: true

rolling

强制在每次响应的时候设置一个 session 标志符 cookie。expiration 重新被设置为初始的 maxAge,重置 expiration 倒计时。

默认值为 false。

注意:当该选项被设置为 true 但是 saveUninitialized 选项被设置为 false,则不会在具有未初始化的 session 响应中设置 cookie。

saveUninitialized

强制将未初始化的 session 保存回 store。当一个 session 是新的但是还未被修改时我们说他是未初始化的。选择 false 值对实现登录 session 是有用的,因为它减少了服务器存储的用量,遵守了设置 cookie 前需要许可的规则。选择 false 值也有助于客户端在没有回话的情况下发出过个并行请求的竞争条件。

默认值为 true,但是不推荐使用默认值,因为默认值将来会被更改。请研究此项设置并选择适合你的用例的选项。

注意:如果你正在结合 PassportJS 使用 Session,用户通过身份验证后PassportJS 将为该用户在 session 中添加一个空的 Passport 对象,这将会视为对 session 的修改,导致 session 被保存。这已经在 PassportJS 0.3.0 中被修复。

secret

必设选项

这是用来给 session ID cookie 签名的 secret。这可以是单个 secret 的字符串也可以是多个 secret 组成的数组。如果提供了一组 secrets,只有第一个元素会被用来给 session ID cookie 签名,在验证请求签名的时候才会考虑到所有元素。

store

session 存储实例,默认为一个新的 MemoryStore 实例。

unset

控制取消设置 req.session 的结果(通过删除,设为 null,等等)。

默认值为 'keep'

  • 'destroy' 表示当响应结束的时候 session 将会被销毁(删除)。
  • 'keep' 表示在 store 中的 session 会被保留,但是在请求期间做的修改将会被忽略不会被保存。

req.session

存储或者访问 session 数据,只需要使用请求属性 req.session,该属性(通常)由 store 序列化为 session,所以一般来说嵌套对象也可以接受。下面的示例是一个基于特定用户的视图计数器:

// Use the session middleware
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
 
// Access the session as req.session
app.get('/', function(req, res, next) {
  if (req.session.views) {
    req.session.views++
    res.setHeader('Content-Type', 'text/html')
    res.write('<p>views: ' + req.session.views + '</p>')
    res.write('<p>expires in: ' + (req.session.cookie.maxAge / 1000) + 's</p>')
    res.end()
  } else {
    req.session.views = 1
    res.end('welcome to the session demo. refresh!')
  }
})

Session.regenerate(callback)

要重新生成 session 只需要调用这个方法。完成后一个新的 SID 和 Session 实例将会被初始化在 req.session 并且 callback 会被调用。

req.session.regenerate(function(err) {
  // will have a new session here
})

Session.destroy(callback)

销毁 Session 并取消设置 req.session 属性。完成后将调用 callback。

req.session.destroy(function(err) {
  // cannot access session here
})

Session.reload(callback)

从 store 重新载入 session 数据并重新填充 req.session 对象。完成后将调用 callback。

req.session.reload(function(err) {
  // session updated
})

Session.save(callback)

将 session 保存回 store,用内存中的内容替换 store 中的内容(尽管 store 可能还会做其他的事情—参阅 store 的文档以了解其确切的行为)。

如果 session 数据被改变了这个方法会在 HTTP 响应的末尾自动被调用(尽管这个行为可以被中间件构造器中的多种选项所改变)。因此,一般来讲这个方法不需要被手动调用。

存在一些调用这个方法会很有用的情况,比如重定向,长期请求(long-lived requests)或着 WebSockets。

req.session.save(function(err) {
  // session saved
})

Session.touch(callback)

更新 .maxAge 属性。一般来讲这个方法不需要被调用因为 session 中间件为你执行了这个操作。

req.session.id

每一个 session 都有一个与之关联的唯一 ID。该属性是 req.sessionID 的别名而且无法修改。该属性已被添加以使 session ID 可以从 session 对象中访问。

req.session.cookie

每一个 session 都有一个唯一的 cookie 与之伴随。这允许你更改每个访问者的 session cookie。例如我们可以设置 req.session.cookie.expires 为 false 来使 cookie 仅在用户-代理的持续时间中保留。

Cookie.maxAge

req.session.cookie.maxAge 将以毫秒数返回剩余的时间,我们也可以重新分配一个新值来适当地调整 .expires 属性。以下代码是等效的:

var hour = 3600000
req.session.cookie.expires = new Date(Date.now() + hour)
req.session.cookie.maxAge = hour

例如当 maxAge 被设置为 60000(一分钟)时,三十秒后它将返回 30000 知道当前的请求已完成,此时调用 req.session.touch() 将会重设 req.session.maxAge 为它的初始值。

req.session.cookie.maxAge // => 30000

req.sessionID

要拿到载入的 session 的 ID,访问请求的属性 req.sessionID。当 session 被载入或被创建的时候这仅是一个只读的值。

Session Store Implementation

每一个 session store 必须是一个 EventEmitter 并且实现特定的方法。下列的方法是必需,推荐和可选的列表。

  • 必需的方法是此模块将会始终在 store 中调用的方法。
  • 推荐的方法是如果可用此模块将会在 store 中调用的方法。
  • 可选的方法是此模块根本不会调用的方法

有关示例实现请查看 connect-redis 仓库。

store.all(callback)

可选

该可选方法用于以数组形式获取 store 中的所有 session。回调方法应该使用为 callback(error, sessions)

store.destroy(sid, callback)

必需

该必需方法根据给定的 session ID 来销毁(删除)store 中的 session。session被删除后回调函数应该使用为 callback(error)

store.clear(callback)

可选

该可选方法用于删除 store 中的所有 session。store 清空后回调函数应该使用为 callback(error)

store.length(callback)

可选

该可选方法用于获取 store 中所有 session 的个数。回调函数应该使用为 callback(error, len)

store.get(sid, callback)

必需

该必需方法根据给定的 session ID 从 store 中获取 session。回调函数应该使用为 callback(error, session)

如果找到 session 回调函数中的 session 参数应该为一个 session 对象,否则如果没有找到 session(并且也没有错误)应该为 null 或 undefined。当 error.code === 'ENOENT' 表现为 callback(null, null) ,这是一种特殊情况。

store.set(sid, session, callback)

必需

该必需方法根据给定的 session ID 和 session 对象将 session 存入 store。session 存入 store 后回调函数应该使用为 callback(error)

store.touch(sid, session, callback)

推荐

该推荐方法根据给定的 session ID 和 session 对象 "触碰" 给定的 session 对象。session 被 "触碰" 后回调函数应该使用为 callback(error)

该方法主要用于 store 自动删除空闲 session,并将此方法用于向 store 发送给定 session 处于活动状态的信号,可能回重置空闲计时器。

Compatible Session Stores

下列的模块是实现了一个和本模块兼容的 session store。请提出 PULL REQUEST 来添加其他的模块 :)

本处仅列出两处 store 实现,更多请查看原文档

connect-db2: 一个使用 ibm_db 模块建成的基于 IBM DB2 的 session store。

connect-mongo: 一个基于 SQL Server 的 session store。

Example

一个简单使用 express-session 来为用户存储页面访问的例子:

var express = require('express')
var parseurl = require('parseurl')
var session = require('express-session')
 
var app = express()
 
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))
 
app.use(function (req, res, next) {
  if (!req.session.views) {
    req.session.views = {}
  }
 
  // get the url pathname
  var pathname = parseurl(req).pathname
 
  // count the views
  req.session.views[pathname] = (req.session.views[pathname] || 0) + 1
 
  next()
})
 
app.get('/foo', function (req, res, next) {
  res.send('you viewed this page ' + req.session.views['/foo'] + ' times')
})
 
app.get('/bar', function (req, res, next) {
  res.send('you viewed this page ' + req.session.views['/bar'] + ' times')
})

License

MIT

Keywords

none