Express服务端如何实现Basic认证的Authorization头1分钟过期,强制客户端重新认证
核心问题根因
你现在遇到的凭证长期不失效的问题,本质是两个机制的冲突:
- 浏览器侧固有行为:Basic Auth的凭证一旦被用户首次确认保存,会被绑定到当前站点的凭证库持久化存储,后续请求自动携带,不会主动过期
- 现有服务端的逻辑漏洞:只要检测到请求携带了格式合法、凭证匹配的Authorization头,不管当前会话状态如何,都会直接校验通过并生成新的认证会话——相当于给浏览器自动携带的旧凭证开了永久通行的后门,哪怕session的60秒有效期到了,旧凭证依然能直接换一个新的有效会话,自然实现不了1分钟强制重登的要求。
落地改造方案
所有改造仅需在服务端完成,完全不需要调整客户端浏览器配置,步骤如下:
1. 独立维护认证有效期的服务端锚点
仅靠express-session的Cookie MaxAge做判断是不够的——session一旦因为过期被客户端清除,你就丢失了「这个客户端上次完成认证的时间点」的记录。
解决方法是在服务端维护一个内存映射表,不依赖任何客户端存储,专门记录每个客户端的认证通过时间。因为你要求的有效期仅为1分钟,内存存储完全够用,超过有效期的记录定期清理即可,不需要做持久化:
// 映射表key:`客户端IP-用户名`,value:认证通过的时间戳 const authTimestampMap = new Map(); // 每30秒扫描清理一次超过1分钟的过期记录 setInterval(() => { const expireThreshold = Date.now() - 60 * 1000; for (const [key, authTime] of authTimestampMap.entries()) { if (authTime < expireThreshold) authTimestampMap.delete(key); } }, 30_000);
2. 改造未认证响应逻辑,破局浏览器自动存凭证的机制
浏览器存储Basic Auth凭证的匹配规则是:以「站点源 + WWW-Authenticate头里的realm值」为索引维度,只要realm值和之前保存的一致,浏览器就会自动携带对应凭证,不会弹框让用户重新输入。
对应改造就是,返回401未认证响应时,给realm拼上动态的时间戳标识,让每次需要重登的时候realm都是新值,浏览器自然不会自动匹配旧的存储凭证:
function authenticate(res) { // realm 加当前秒级时间戳,避免浏览器自动匹配之前存的旧凭证 const dynamicRealm = `Access to the staging site - ${Math.floor(Date.now() / 1000)}`; res.setHeader("WWW-Authenticate", `Basic realm="${dynamicRealm}"`); res.status(401).send(`<h1>Unauthorized 😢!</h1>`); }
3. 重构路由校验逻辑,堵上旧凭证自动续期的漏洞
把原来的路由判断逻辑调整为三层校验,彻底切断旧凭证的永久通行路径:
app.route("/private").get( (req, res, next) => { // 第一层校验:现有会话还在有效期内,直接放行,同时刷新认证时间戳 if (req.session.authenticated) { const clientKey = `${req.ip}-${req.session.username}`; authTimestampMap.set(clientKey, Date.now()); return next(); } // 会话已过期,进入重新校验流程 const authorization = req.get("Authorization"); if (!authorization) return authenticate(res); // 解析请求头携带的凭证 const [, credentialsRaw] = authorization.split(" "); const [username, password] = Buffer.from(credentialsRaw, "base64").toString("ascii").split(":"); // 第二层校验:先判断凭证本身是否合法 if (!(username in users) || users[username].password !== password) { return authenticate(res); } // 第三层校验:查服务端记录的上次认证时间,判断是否已超1分钟有效期 const clientKey = `${req.ip}-${username}`; const lastAuthTime = authTimestampMap.get(clientKey); const isExpired = !lastAuthTime || (Date.now() - lastAuthTime > 60_000); // 哪怕凭证合法,只要过了有效期就不放行,返回带动态realm的401触发重登 if (isExpired) return authenticate(res); // 校验通过后,重新创建会话、记录认证信息 req.session.authenticated = true; req.session.username = username; authTimestampMap.set(clientKey, Date.now()); next(); }, (req, res) => { res.send(`<h1>Congratulations! You've accessed the private page! 👍</h1>`); } );
改造后的实际运行流程
- 用户首次访问时,浏览器弹出认证框让用户输入账号密码,服务端校验通过后记录认证时间,创建60秒有效期的会话
- 60秒有效期内,用户可正常访问受保护页面,服务端会自动续期认证时间戳
- 超过1分钟后,客户端存储的session过期,哪怕浏览器自动携带之前存的旧Authorization头,服务端查到上次认证时间已超阈值,会返回带动态realm的401响应
- 浏览器发现当前请求要求的realm和之前存的凭证绑定的realm不匹配,不会自动携带旧凭证,必须弹出输入框让用户重新完成认证,完全符合1分钟强制重登的预期。
内容的提问来源于stack exchange,提问作者Parzh
相关产品推荐
相关产品推荐

