Express 是一个 Node.js 的 Web 框架,提供对外服务器的功能。中间件则是 Express 提供的一种扩展能力的插件机制。

express-session 就是 Express 的一个中间件。使用 sessionId 的机制,为用户在网站访问期间,提供会话数据的存储支持。
技术实现上,express-session 就是为每个用户生成唯一的一个 sessionId(默认通过名为 connect.sid
的 cookie 字段)并存储在服务器上。在后续请求往返间,后端通过这个 sessionId 就能拿到之前存储的数据,实现用户访问状态的记忆。
❝
注意:会话数据的存储往往会借助文件系统或者数据库系统(生产上通常叫缓冲数数据库,比如 redis)等。express-session 管数据存储叫 Store,默认使用的是内存(
MemoryStore
),不过生产上并不推荐。❞

安装 & 简单使用
express-session 依赖 express,因此使用时需要保证 express 也存在。
$ npm install express express-session
下面是一个简单的使用。
var express = require('express')
var session = require('express-session')
var app = express()
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
secret
是必填项,作为生成 sessio ID 的盐值。resave
、saveUninitialized
都是选填项,不过由于这 2 个选项的默认值会在未来版本修改,因此官方推荐显式传入。
express-session 是通过中间件方式注入到 express 应用中的。经 express-session 处理后的请求实例 req
都包含一个 .session
属性,我们是通过在 .session
属性上存储信息,实现前后请求会话数据的保存的。
以下,我们将通过 2 个复杂一点的案例来介绍 express-session 的使用。
案例介绍
这里举了 2 个例子,一个是统计用户页面访问次数,还有一个是用户登录的例子。
统计页面访问次数
我们先亮代码(不是很多)。
var express = require('express')
var session = require('express-session')
var app = express()
// 1)
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use(function (req, res, next) {
// 2)
if (!req.session.views) {
req.session.views = {}
}
// get the url pathname
var pathname = req.path
// 3)
// 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)
这里我们起了一个监听在 3000 端口的服务器,对访问 /foo、/bar 页面的次数做了统计。
-
首先 express(session({ ... }))
一下,做好会话存储准备,调用后,会在每一次请求(req
)添加一个.session
对象属性 -
页面访问数据存储在 req.session.views
对象属性上,初次访问时是没有这个对象的,就创建({}
) -
接下来获取某个访问路径下( req.path
)的访问次数(+1
),结束
用户登录
用户登录是一个稍微复杂一点的例子,分登录和退出,我们拆开来讲。
首先,我们针对用户登录和未登录状态来区别显示首页内容:
-
用户已登录状态下,显示用户名、暴露退出入口 -
用户未登录状态下,显示登录表单,登录请求发送至 /login
以下是代码实现:
var escapeHtml = require('escape-html')
var express = require('express')
var session = require('express-session')
var app = express()
// 1)
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
// 2.1) middleware to test if authenticated
function isAuthenticated (req, res, next) {
if (req.session.user) next()
else next('route')
}
// 2)
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>')
})
// 3)
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.listen(3000)
这里我们起了一个监听在 3000
端口的服务器,根据登录状态处理首页展示逻辑。
-
还是老样子,首先 express(session({ ... }))
一下,做好会话存储准备,这一步会在每一次请求(req
)添加一个.session
对象属性 -
先跑第一个 /
路径逻辑,这一步会先经过isAuthenticated
中间件校验 -
用户登录后,我们会创建一个 req.session.user
属性存储是用户数据,isAuthenticated
是检查这个属性又没有的,有req.session.user
话,就说明登陆了,展示用户信息(next()
);没有req.session.user
的话,说明未登录,则忽略用户信息展示,跳转至下一个路由处理(next('route')
,也就是第 3 步) -
经过上一步,到这一步说明用户未登录,我们就发送一个登录表单,让用户填写。登录表单包含 user
、pass
字段信息。
接下来,我们来看看 /login
页面的处理逻辑。
// ...
// 1)
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
// 1)
req.session.regenerate(function (err) {
if (err) next(err)
// store user information in session, typically a user id
// 2)
req.session.user = req.body.user
// save the session before redirection to ensure page
// load does not happen before session is saved
// 3)
req.session.save(function (err) {
if (err) return next(err)
// 4)
res.redirect('/')
})
})
})
// ...
-
为了能正确处理 <form>
表单提交数据,我们使用express.urlencoded({ extended: false })
中间件将 form 表单数据收集到req.body
上 -
我们一上来并没有立即对 req.body.user
/req.body.pass
进行校验,而是调用了req.session.regenerate()
重新生成用户会话 sessionId,这能避免会话固定攻击(session fixation attack) -
接下来,为了做简单演示,我们没有校验密码,而是直接将提交的用户名存储下来( req.session.user = req.body.user
) -
在重定向会首页之前,我们又调用了 req.session.save()
将新 sessionId 下的 user 信息同步给 Store(默认是缓存,实际生产往往是一个缓存数据库(像 redis)) -
最后,重定到首页,这时候页面就显示登录用户名了
再来看看退出登录(/logout
)的逻辑。
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 sessionId
// does not have a logged in user
// 1)
req.session.user = null
// 2)
req.session.save(function (err) {
if (err) next(err)
// regenerate the session, which is good practice to help
// guard against forms of session fixation
// 3)
req.session.regenerate(function (err) {
if (err) next(err)
// 4)
res.redirect('/')
})
})
})
-
首先,我们将 req.session.user
置为空 -
然后, req.session.save()
将上面的修改同步到 Store -
接着,通过调用 req.session.regenerate()
重新生成 sessionId,这块跟登录一样,是为了避免会话固定攻击 -
最后,重定到首页,这时候页面就未登录状态下的登录框了
总结
express-session 是用来为 express 框架提供会话缓存支持的一个中间件。技术上是通过使用 sessionId 机制提供会话记忆支持的。
本文分别列举了 2 个案例来说明 express-session 的使用:访问次数和用户登录。不过需要注意的是,不管是登录还是退出,都要有一个新生成 sessionId 的过程(req.session.regenerate()
),这是为了避免会话固定攻击。
希望本篇文章对你有所帮助,感谢阅读,Happy Coding!
原文始发于微信公众号(写代码的宝哥):express-session:sessionId 机制驱动的一个 express 会话数据存储库
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243675.html