# express+session实现简易身份认证
30 秒速记
- 套路:登录校验密码 → 服务端存
session→ 浏览器只拿一个session id的cookie→ 后续请求凭它查人 - 登录成功先
req.session.regenerate()换新id,防会话固定 - 退出 =
req.session.destroy()删服务端记录 +res.clearCookie()清浏览器 - 默认的
MemoryStore只能本地玩:重启就丢、会泄漏内存、多实例不共享,生产换Redis这类共享存储 cookie配httpOnly(默认就开)、secure、sameSite,写操作还要防CSRF
express-session 做登录,本质是「数据放服务端,浏览器只拿一张号码牌」。 用户登录成功后,服务端把用户名写进 req.session,中间件负责生成一个随机的 session id、签名后通过 Set-Cookie 发给浏览器;之后每个请求带着这张号码牌来,服务端凭它找回会话数据,判断你是谁。我做这类功能会盯三件事:登录后要 regenerate 换号,退出要在服务端真删,生产环境的 store 必须是多实例共享的。
理解「express+session实现简易身份认证」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 security 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1登录成功后前端存个 isLogin = true,刷新就没了,这算身份认证吗?
不算,那只是页面里的一个变量,后端根本不知道。认证状态得在服务端,每个受保护的接口都要从 req.session 里查一遍,前端那个变量顶多用来决定显示哪个按钮。
追问 2从一台机器扩到两台,用户登录后翻一页就变回未登录,为什么?
会话存在第一台的内存或本地文件里,第二个请求被负载均衡打到另一台,那里查不到这个 session id。正解是换共享 store,比如 connect-redis;nginx 的 ip_hash 粘性会话能临时止血,但一台挂了那批用户还是全掉线。
追问 3登录接口返回成功,一跳转又回到登录页,你先看什么?
先看网络面板:登录响应里有没有 Set-Cookie,下一个请求有没有把它带上。没带的话常见原因是 secure: true 但走的是 HTTP,或者跨域请求没开 credentials。带了还不行,再看服务端 store 里有没有这条记录。
追问 4登录成功为什么要调 regenerate,直接往 req.session 里写用户名不行吗?
不换 id 会有会话固定攻击:攻击者先拿一个自己的 session id 塞给受害者,受害者用它登录后,攻击者手里那个 id 也就变成已登录了。regenerate 会作废旧 id、发一个新的,这一步很便宜,没理由省。
# 环境初始化
30 秒速记
express -e:用express-generator生成骨架,-e表示模板用EJSnpm install装骨架自带的依赖,脚手架本身不帮你装npm install express-session session-file-store:会话中间件 + 把会话存成本地文件的storenpm 5以后--save是默认行为,加不加都会写进package.jsonsession-file-store只适合单机演示,多实例生产换connect-redis之类的共享存储
环境初始化就三步:脚手架生成项目、装基础依赖、再装会话相关的两个包。 express -e 生成一个用 EJS 模板的 Express 骨架,这一步只建文件,不装依赖,所以紧接着要 npm install。然后装 express-session 负责会话逻辑,session-file-store 负责把会话数据写到项目下的 sessions 目录,方便你打开文件直接看里面存了什么。拿来学原理很直观,但上线别用文件存储,多台机器之间是不共享的。
首先,初始化项目
express -e
然后,安装依赖。
npm install
接着,安装session相关的包。
npm install --save express-session session-file-store
面试官追问
追问 1执行 express -e 提示命令不存在,怎么办?
express 命令来自 express-generator,要先 npm i -g express-generator。现在更推荐不全局装,直接 npx express-generator -e,版本也不会跟别人机器上的对不上。
追问 2本地能跑,同事拉代码后启动报 Cannot find module 'express-session',问题在哪?
八成是你装的时候没写进 package.json,只躺在自己的 node_modules 里。看一下 dependencies 有没有这一项,再确认 package-lock.json 一起提交了。在干净目录 npm ci 跑一遍,是最快的验证办法。
追问 3session-file-store 存的会话文件会越堆越多吗?
它有个 reapInterval 参数,默认每小时清一次过期文件,所以不会无限增长。但这只是本机文件,进程多开几个、机器多加几台就各管各的,这也是它只适合演示 的原因。
追问 4这个演示项目要改成生产服务,依赖要换哪一个?
把 session-file-store 换成 connect-redis 加一个 Redis 客户端,express-session 本身不用换。所有实例连同一个 Redis,会话自然共享,过期也交给 Redis 的 TTL 处理。
# session相关配置
30 秒速记
name:cookie名,默认connect.sid,改个名可以少暴露技术栈secret:给session id签名的密钥,可以传数组做轮换,别硬编码在代码里store:会话数据放哪,示例是本地文件,生产用RedissaveUninitialized: false:没往session写过东西就不落库、不发cookie,匿名访客不产生垃圾会话resave: false:会话没改就不回写,避免并发请求互相覆盖;store支持touch时设false就行cookie.maxAge单位是毫秒,示例10 * 1000就是10秒,方便看过期效果
这段配置决定了四件事:cookie 叫什么、拿什么签名、数据存哪、多久过期。 最容易被问的是 saveUninitialized 和 resave。saveUninitialized: false 的意思是用户没登录、你也没往 req.session 写过东西,就不创建会话,否则每个路过的访客都会生成一个会话文件;resave: false 是会话内容没变就不重新写回存储,两个并发请求都回写的话,后完成的那个可能把前一个的修改覆盖掉。示例里 maxAge 只有 10 秒,是故意为了演示过期,真实项目一般是小时或天级别。
配置如下,并不复杂,可以见代码注释,或者参考官方文档。
var express = require('express');
var app = express();
var session = require('express-session');
var FileStore = require('session-file-store')(session);
var identityKey = 'skey';
app.use(session({
name: identityKey,
secret: 'chyingp', // 用来对session id相关的cookie进行签名
store: new FileStore(), // 本地存储session(文本文件,也可以选择其他store,比如redis的)
saveUninitialized: false, // 是否自动保存未初始化的会话,建议false
resave: false, // 是否每次都重新保存会话,建议false
cookie: {
maxAge: 10 * 1000 // 有效期,单位是毫秒
}
}));
面试官追问
追问 1公开首页没登录,每次访问都会多出一个会话文件,最可能是哪项配置的问题?
saveUninitialized 被设成了 true,未初始化的会话也会被保存并下发 cookie。改回 false;如果还在产生,看看是不是有别的中间件往 req.session 里写了东西,比如记录访问来源。
追问 2用户反馈每隔 10 秒就要重新登录,服务端也没报错,怎么回事?
cookie.maxAge 还是示例里的 10 * 1000,浏览器 10 秒后就把 cookie 扔了。改成业务需要的时长,同时让 store 的过期时间对齐;想要「有操作就续期」再加 rolling: true。
