黑人生命也是命。
支持平等司法倡议.

express-session

NPM Version NPM Downloads Build Status Test Coverage

安装

这是一个 Node.js 模块,可通过 npm 注册表 获得。安装使用 npm install 命令 完成

$ npm install express-session

API

var session = require('express-session')

session(options)

使用给定的 options 创建一个会话中间件。

注意 会话数据不会保存在 cookie 本身中,只保存会话 ID。会话数据存储在服务器端。

注意 自 1.5.0 版本起,不再需要使用 cookie-parser 中间件 来使此模块工作。此模块现在直接在 req/res 上读取和写入 cookie。如果 secret 在此模块和 cookie-parser 之间不相同,使用 cookie-parser 可能会导致问题。

警告 默认的服务器端会话存储,MemoryStore故意不设计用于生产环境。在大多数情况下它会泄露内存,无法扩展到单个进程之外,并且仅用于调试和开发。

有关存储列表,请参见 兼容的会话存储

选项

express-session 接受选项对象中的这些属性。

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

以下是在此对象中可以设置的选项。

cookie.domain

指定 Domain Set-Cookie 属性的值。默认情况下,不设置域,并且大多数客户端会认为 cookie 仅适用于当前域。

cookie.expires

指定 Date 对象作为 Expires Set-Cookie 属性的值。默认情况下,不设置过期时间,并且大多数客户端会将其视为“非持久性 cookie”,并在退出 Web 浏览器应用程序等条件下将其删除。

注意如果在选项中同时设置了 expiresmaxAge,则使用对象中定义的最后一个选项。

注意不应直接设置 expires 选项;而应仅使用 maxAge 选项。

cookie.httpOnly

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

注意在将此项设置为 true 时要小心,因为符合条件的客户端不允许客户端 JavaScript 在 document.cookie 中查看 cookie。

cookie.maxAge

指定在计算 Expires Set-Cookie 属性时要使用的 number(以毫秒为单位)。这是通过获取当前服务器时间并将 maxAge 毫秒添加到该值来计算 Expires 日期时间来完成的。默认情况下,不设置最大年龄。

注意如果在选项中同时设置了 expiresmaxAge,则使用对象中定义的最后一个选项。

cookie.path

指定 Path Set-Cookie 的值。默认情况下,将其设置为 '/',这是域的根路径。

cookie.sameSite

指定 booleanstring 作为 SameSite Set-Cookie 属性的值。默认情况下,此值为 false

  • trueSameSite 属性设置为 Strict 以严格执行同站策略。
  • false 将不设置 SameSite 属性。
  • 'lax' 将为宽松同站强制设置 SameSite 属性为 Lax
  • 'none' 将为显式跨站 cookie 设置 SameSite 属性为 None
  • 'strict' 将为严格同站强制设置 SameSite 属性为 Strict

有关不同强制级别的更多信息,请参阅 规范

注意 这是一个尚未完全标准化的属性,将来可能会发生更改。这也意味着许多客户端可能在理解此属性之前会忽略它。

注意 有一个 草案规范 要求在 SameSite 属性已设置为 'none' 时将 Secure 属性设置为 true。一些网络浏览器或其他客户端可能会采用此规范。

cookie.secure

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

注意 在将此设置为 true 时要小心,因为如果浏览器没有 HTTPS 连接,则符合要求的客户端将来不会将 cookie 发送回服务器。

请注意,secure: true 是一个推荐选项。但是,它需要一个启用 https 的网站,即 HTTPS 对于安全 cookie 是必需的。如果设置了 secure,并且您通过 HTTP 访问您的网站,则不会设置 cookie。如果您将 node.js 放在代理后面并且使用 secure: true,则需要在 express 中设置“信任代理”

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

要在生产中使用安全 cookie,但允许在开发中进行测试,以下是在 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 提供,在使用此设置时要小心,因为一旦在 HTTPS 上设置 cookie,它将不再通过 HTTP 可见。当 Express "trust proxy" 设置正确设置以简化开发与生产配置时,这很有用。

genid

要调用的函数以生成新的会话 ID。提供一个函数,该函数返回一个字符串,该字符串将用作会话 ID。如果您要在生成 ID 时使用附加到 req 的某个值,则该函数将 req 作为第一个参数提供。

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

注意 小心生成唯一的 ID,以免您的会话发生冲突。

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

在响应中设置会话 ID Cookie 的名称(并在请求中读取)。

默认值为 'connect.sid'

注意如果在同一主机名上运行多个应用(这只是名称,例如 localhost127.0.0.1;不同的方案和端口不会命名不同的主机名),则需要将会话 Cookie 相互分离。最简单的方法是为每个应用设置不同的 name

代理

在设置安全 Cookie 时信任反向代理(通过“X-Forwarded-Proto”标头)。

默认值为 undefined

  • true 将使用“X-Forwarded-Proto”标头。
  • false 忽略所有标头,并且仅在存在直接 TLS/SSL 连接时才将连接视为安全。
  • undefined 使用 express 中的“信任代理”设置
重新保存

强制将会话保存回会话存储,即使会话在请求期间从未修改过。根据您的存储,这可能是有必要的,但它也可能创建竞争条件,其中客户端向您的服务器发出两个并行请求,并且在一个请求中对会话所做的更改可能会在另一个请求结束时被覆盖,即使它没有进行任何更改(此行为还取决于您使用的存储)。

默认值为 true,但使用默认值已被弃用,因为默认值将来会更改。请研究此设置并选择适合您用例的内容。通常,您需要 false

我如何知道这对我的商店是否必要?了解的最佳方法是检查您的商店是否实现了 touch 方法。如果是,则可以安全地设置 resave: false。如果它没有实现 touch 方法并且您的商店在存储的会话上设置了到期日期,那么您可能需要 resave: true

滚动

强制在每次响应中设置会话标识符 Cookie。到期时间重置为原始 maxAge,重置到期倒计时。

默认值为 false

启用此功能后,会话标识符 Cookie 将在自上次发送响应以来 maxAge 中过期,而不是自服务器上次修改会话以来 maxAge 中过期。

这通常与短的、非会话长度的 maxAge 值结合使用,以快速超时会话数据,并减少在进行的服务器交互期间发生这种情况的可能性。

注意 当此选项设置为 truesaveUninitialized 选项设置为 false 时,cookie 将不会在具有未初始化会话的响应上设置。此选项仅在为请求加载现有会话时修改行为。

saveUninitialized

强制将“未初始化”的会话保存到存储中。会话在新建但未修改时处于未初始化状态。选择 false 对于实现登录会话、减少服务器存储使用或遵守在设置 cookie 之前需要获得许可的法律非常有用。选择 false 还可以帮助解决客户端在没有会话的情况下发出多个并行请求时的竞争条件。

默认值为 true,但使用默认值已被弃用,因为默认值将在将来更改。请研究此设置并选择适合您用例的内容。

注意 如果您将 Session 与 PassportJS 结合使用,Passport 将向会话中添加一个空的 Passport 对象,以便在用户经过身份验证后使用,这将被视为对会话的修改,从而导致其被保存。此问题已在 PassportJS 0.3.0 中得到修复

secret

必需选项

这是用于签名会话 ID cookie 的密钥。它可以是单个密钥的字符串,也可以是多个密钥的数组。如果提供了密钥数组,则仅使用第一个元素来签名会话 ID cookie,而在请求中验证签名时将考虑所有元素。密钥本身不应该容易被人解析,最好是一组随机字符。最佳实践可能包括

  • 使用环境变量来存储密钥,确保密钥本身不存在于您的存储库中。
  • 定期更新密钥,同时确保以前的密钥在数组中。

使用无法猜测的密钥将劫持会话的能力仅降低为猜测会话 ID(由 genid 选项确定)。

更改密钥值将使所有现有会话无效。为了在不使会话无效的情况下轮换密钥,请提供一个密钥数组,其中新密钥为数组的第一个元素,并将以前的密钥作为后面的元素。

store

会话存储实例,默认为新的 MemoryStore 实例。

unset

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

默认值为 'keep'

  • 'destroy' 当响应结束时,会话将被销毁(删除)。
  • 'keep' 存储中的会话将被保留,但在请求期间进行的修改将被忽略且不保存。

req.session

要存储或访问会话数据,只需使用请求属性 req.session,它(通常)由存储序列化为 JSON,因此嵌套对象通常没问题。例如,下面是一个特定于用户的查看计数器

// 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)

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

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

Session.destroy(callback)

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

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

Session.reload(callback)

从存储中重新加载会话数据并重新填充 req.session 对象。完成后,将调用 callback

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

Session.save(callback)

将会话保存回存储,用存储中的内容替换内存中的内容(尽管存储可能执行其他操作——请查阅存储文档了解确切行为)。

如果会话数据已更改,则在 HTTP 响应结束时会自动调用此方法(尽管可以通过中间件构造函数中的各种选项来更改此行为)。因此,通常不需要调用此方法。

在某些情况下,调用此方法很有用,例如重定向、长时间运行的请求或在 WebSockets 中。

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

Session.touch()

更新 .maxAge 属性。通常不需要调用此方法,因为会话中间件会为您执行此操作。

req.session.id

每个会话都有一个与之关联的唯一 ID。此属性是 req.sessionID 的别名,且不能修改。添加此属性是为了使会话 ID 可从 session 对象访问。

req.session.cookie

每个会话都有一个唯一的 cookie 对象随附。这允许您为每个访问者更改会话 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(一分钟)且已过去 30 秒时,它将返回 30000,直到当前请求完成,此时将调用 req.session.touch()req.session.cookie.maxAge 重置为其原始值。

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

Cookie.originalMaxAge

req.session.cookie.originalMaxAge 属性返回会话 cookie 的原始 maxAge(生存时间),以毫秒为单位。

req.sessionID

要获取已加载会话的 ID,请访问请求属性 req.sessionID。这仅仅是在加载/创建会话时设置的一个只读值。

会话存储实现

每个会话存储必须是一个 EventEmitter 并实现特定方法。以下方法是必需推荐可选方法的列表。

  • 必需方法是此模块将始终在存储中调用的方法。
  • 推荐方法是此模块在可用时将在存储中调用的方法。
  • 可选方法是此模块根本不调用的方法,但有助于向用户展示统一的存储。

有关示例实现,请查看 connect-redis 存储库。

store.all(callback)

可选

此可选方法用于以数组形式获取存储中的所有会话。callback 应作为 callback(error, sessions) 调用。

store.destroy(sid, callback)

必需

此必需方法用于根据会话 ID (sid) 从存储中销毁/删除会话。销毁会话后,应将 callback 作为 callback(error) 调用。

store.clear(callback)

可选

此可选方法用于从存储中删除所有会话。清除存储后,应将 callback 作为 callback(error) 调用。

store.length(callback)

可选

此可选方法用于获取存储中所有会话的数量。callback 应作为 callback(error, len) 调用。

store.get(sid, callback)

必需

此必需方法用于根据会话 ID (sid) 从存储中获取会话。callback 应作为 callback(error, session) 调用。

如果找到会话,session 参数应为会话,否则如果未找到会话(且没有错误),则为 nullundefined。当 error.code === 'ENOENT' 时,会进行特殊处理,以执行类似于 callback(null, null) 的操作。

store.set(sid, session, callback)

必需

此必需方法用于根据会话 ID (sid) 和会话 (session) 对象将会话插入或更新到存储中。将会话设置到存储中后,应将回调作为 callback(error) 调用。

store.touch(sid, session, callback)

推荐

此推荐方法用于“触碰”给定的会话,给定会话 ID (sid) 和会话 (session) 对象。一旦会话被触碰,应将 callback 作为 callback(error) 调用。

这主要用于存储将自动删除空闲会话,此方法用于向存储发出信号,表明给定会话处于活动状态,可能会重置空闲计时器。

兼容会话存储

以下模块实现与本模块兼容的会话存储。请提交 PR 以添加其他模块 :)

★ aerospike-session-store 使用 Aerospike 的会话存储。

★ better-sqlite3-session-store 基于 better-sqlite3 的会话存储。

★ cassandra-store 基于 Apache Cassandra 的会话存储。

★ cluster-store 用于在进程内/嵌入式存储中使用的包装器 - 如 SQLite(通过 knex)、leveldb、文件或内存 - 使用节点集群(适用于 Raspberry Pi 2 和其他多核嵌入式设备)。

★ connect-arango 基于 ArangoDB 的会话存储。

★ connect-azuretables 基于 Azure 表存储 的会话存储。

★ connect-cloudant-store 基于 IBM Cloudant 的会话存储。

★ connect-couchbase 基于 couchbase 的会话存储。

★ connect-datacache 基于 IBM Bluemix Data Cache 的会话存储。

★ @google-cloud/connect-datastore 基于 Google Cloud Datastore 的会话存储。

★ connect-db2 使用 ibm_db 模块构建的基于 IBM DB2 的会话存储。

★ connect-dynamodb 基于 DynamoDB 的会话存储。

★ @google-cloud/connect-firestore 基于 Google Cloud Firestore 的会话存储。

★ connect-hazelcast 适用于 Connect 和 Express 的 Hazelcast 会话存储。

★ connect-loki 基于 Loki.js 的会话存储。

★ connect-lowdb 基于 lowdb 的会话存储。

★ connect-memcached 基于 memcached 的会话存储。

★ connect-memjs 使用 memjs 作为 memcached 客户端的基于 memcached 的会话存储。

★ connect-ml 基于 MarkLogic Server 的会话存储。

★ connect-monetdb 基于 MonetDB 的会话存储。

★ connect-mongo 基于 MongoDB 的会话存储。

★ connect-mongodb-session 由 MongoDB 构建并维护的轻量级基于 MongoDB 的会话存储。

★ connect-mssql-v2 基于 connect-mssql 的基于 Microsoft SQL Server 的会话存储。

★ connect-neo4j 基于 Neo4j 的会话存储。

★ connect-pg-simple 基于 PostgreSQL 的会话存储。

★ connect-redis 基于 Redis 的会话存储。

★ connect-session-firebase 基于 Firebase 实时数据库 的会话存储

★ connect-session-knex 使用 Knex.js 的会话存储,Knex.js 是 PostgreSQL、MySQL、MariaDB、SQLite3 和 Oracle 的 SQL 查询构建器。

★ connect-session-sequelize 使用 Sequelize.js 的会话存储,Sequelize.js 是 PostgreSQL、MySQL、SQLite 和 MSSQL 的 Node.js / io.js ORM。

★ connect-sqlite3 SQLite3 会话存储,仿照 TJ 的 connect-redis 存储建模。

★ connect-typeorm 基于 TypeORM 的会话存储。

★ couchdb-expression 基于 CouchDB 的会话存储。

★ dynamodb-store 基于 DynamoDB 的会话存储。

★ express-etcd 基于 etcd 的会话存储。

★ express-mysql-session 通过 node-mysql 模块使用原生 MySQL 的会话存储。

★ express-nedb-session 基于 NeDB 的会话存储。

★ express-oracle-session 通过 node-oracledb 模块使用原生 oracle 的会话存储。

★ express-session-cache-manager 实现 cache-manager 的存储,支持 多种存储类型

★ express-session-etcd3 基于 etcd3 的会话存储。

★ express-session-level 基于 LevelDB 的会话存储。

★ express-session-rsdb 基于 Rocket-Store 的会话存储:一个非常简单、超级快速且功能强大的平面文件数据库。

★ express-sessions 同时支持 MongoDB 和 Redis 的会话存储。

★ firestore-store 基于 Firestore 的会话存储。

★ fortune-session 基于 Fortune.js 的会话存储。支持 Fortune 支持的所有后端(MongoDB、Redis、Postgres、NeDB)。

★ hazelcast-store 基于 Hazelcast 的会话存储,构建在 Hazelcast Node Client 之上。

★ level-session-store 基于 LevelDB 的会话存储。

★ lowdb-session-store 基于 lowdb 的会话存储。

★ medea-session-store 基于 Medea 的会话存储。

★ memorystore 为生产环境打造的内存会话存储。

★ mssql-session-store 基于 SQL Server 的会话存储。

★ nedb-session-store 基于 NeDB(内存或文件持久化)的备用会话存储。

★ @quixo3/prisma-session-store Prisma Framework 的会话存储。

★ restsession 利用 RESTful API 存储会话

★ sequelstore-connect 使用 Sequelize.js 的会话存储。

★ session-file-store 基于文件系统的会话存储。

★ session-pouchdb-store PouchDB/CouchDB 的会话存储。接受嵌入式、自定义或远程 PouchDB 实例和实时同步。

★ session-rethinkdb 基于 RethinkDB 的会话存储。

★ @databunker/session-store 基于 Databunker 的加密会话存储。

★ sessionstore 适用于各种数据库的会话存储。

★ tch-nedb-session 基于 NeDB 的文件系统会话存储。

示例

查看计数器

一个使用 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')
})

app.listen(3000)

用户登录

一个使用 express-session 保持用户登录会话的简单示例。

var escapeHtml = require('escape-html')
var express = require('express')
var session = require('express-session')

var app = express()

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))

// middleware to test if authenticated
function isAuthenticated (req, res, next) {
  if (req.session.user) next()
  else next('route')
}

app.get('/', isAuthenticated, function (req, res) {
  // this is only called when there is an authentication user due to isAuthenticated
  res.send('hello, ' + escapeHtml(req.session.user) + '!' +
    ' <a href="/logout">Logout</a>')
})

app.get('/', function (req, res) {
  res.send('<form action="/login" method="post">' +
    'Username: <input name="user"><br>' +
    'Password: <input name="pass" type="password"><br>' +
    '<input type="submit" text="Login"></form>')
})

app.post('/login', express.urlencoded({ extended: false }), function (req, res) {
  // login logic to validate req.body.user and req.body.pass
  // would be implemented here. for this example any combo works

  // regenerate the session, which is good practice to help
  // guard against forms of session fixation
  req.session.regenerate(function (err) {
    if (err) next(err)

    // store user information in session, typically a user id
    req.session.user = req.body.user

    // save the session before redirection to ensure page
    // load does not happen before session is saved
    req.session.save(function (err) {
      if (err) return next(err)
      res.redirect('/')
    })
  })
})

app.get('/logout', function (req, res, next) {
  // logout logic

  // clear the user from the session object and save.
  // this will ensure that re-using the old session id
  // does not have a logged in user
  req.session.user = null
  req.session.save(function (err) {
    if (err) next(err)

    // regenerate the session, which is good practice to help
    // guard against forms of session fixation
    req.session.regenerate(function (err) {
      if (err) next(err)
      res.redirect('/')
    })
  })
})

app.listen(3000)

调试

此模块在内部使用 debug 模块来记录有关会话操作的信息。

要查看所有内部日志,请在启动应用程序(在本例中为 npm start)时将 DEBUG 环境变量设置为 express-session

$ DEBUG=express-session npm start

在 Windows 上,使用相应的命令;

> set DEBUG=express-session & npm start

许可证

MIT