我有一个使用快速框架的nodejs Web应用程序,可以通过Internet访问。 我正在使用一个会话存储,它将会话存储为磁盘上的纯文件,并且在当前的实现中,每个没有cookie的请求都将获得一个新的会话ID,从而在磁盘上为新会话创建一个新文件。
由于该应用程序可以通过互联网访问,因此我收到了很多无效请求,这些请求当然从不发送cookie,而是在我的文件系统上产生更多会话,这真是一团糟。
我使用 OWASP 会话管理备忘单作为实施的指南(https://www.owasp.org/index.php/Session_Management_Cheat_Sheet),但它没有详细涵盖来宾会话的主题。它仅声明应用程序可能会发现将会话也分配给未经身份验证的(来宾)用户很有用,因此来宾会话通常似乎是一个有效的功能。
所以现在我不知道如何正确解决无效/恶意请求不必要地创建的会话/会话文件的问题。有什么推荐的方法可以做到这一点吗?
我想到了"访客"会话的非常短的过期时间(<5 分钟)和 IP 范围或其他内容的白名单的组合,其中不在白名单中的任何 IP 都不会收到访客会话(但当然会话一旦成功通过身份验证)。
关于我应该如何处理这个问题的任何提示?
无论您如何存储会话,您都将面临同样的问题。在某些时候,您的会话存储将溢出(磁盘空间不足,RAM耗尽,索引节点耗尽等)。
您需要做的是修剪会话。除非您真的有能力无限期地存储会话,否则您应该在会话 cookie 上设置到期日期。对于客户端,浏览器将负责删除cookie。对于服务器,您需要定期检查所有会话以查看是否有任何会话已过期。
你接下来要做的事情很简单。无论您选择哪种技术来存储会话,您都只需删除过期的会话即可。这可以在您的节点进程内(在某些setTimeout()
处理程序中)或在您的节点进程外部(可能是简单的日常 cron 作业)完成。
在删除过时的会话文件之前,您应该留出一些宽限期(1 分钟、1 小时、1 天等),以防止删除会话文件与用户加载网站之间的争用情况。
您可能还希望允许用户在每次访问时刷新会话到期日期。对于基于文件的会话存储,这可以像触摸文件以更新上次修改时间一样简单。
有一种情况是这种策略不起作用。出于性能原因,当您删除数据时,某些数据库不会释放磁盘空间(例如,带有InnoDB的MySQL)。相反,数据只是标记为已删除,但数据库仍在不断增长。在这种情况下,您唯一的出路是更改会话存储。但是,由于您使用的是文件存储,因此这不是您需要担心的问题。
最适合您的用例的会话存储是 Redis、Memcached 或其他一些快速内存数据存储,但请注意,您的所有会话数据都必须适合 RAM。
另一种选择是使用基于磁盘的数据库,如任何RDBMS或Mongo,Couch,Rethink等,但要确保它很快,否则您的性能将大大降低。
具有最高可扩展性特征的最快方法是不在您的服务器上存储任何会话数据,而是依赖于在 cookie 或其他客户端存储中发送的数据,例如使用 JWT - 请参阅:https://jwt.io/- 但请注意,这样您将无法控制曾经颁发的会话令牌,除非您引入数据库来检查它们是否有效以及使它们无效的机制, 但是在这一点上,您会遇到与在服务器上存储该数据相同的问题,也许有一个例外,即要存储的数据可能更少,并且不必经常更新。
这里的每种方法都有其优点和缺点,但将数据存储在文件系统上的文件中从来都不是生产任何数据的最佳解决方案,而不仅仅是会话数据。您应该为此使用数据库,或者如果在您的用例中可以接受其缺点,则应在客户端上存储数据。
这是您要避免的事情:
app.use(session({ ... }));
app.use(express.static(...));
这将为所有静态请求创建会话。
您可以通过禁用saveUninitialized
设置来缓解这种情况:
app.use(session({
saveUninitialized : false,
...
}));
app.use(express.static(...));
这将防止存储新的但未修改的会话。由于静态资源不会修改会话,因此不会为它们创建任何会话。
另一种选择是仅为路由的子集启用会话:
const session = require('express-session');
let sessionMiddleware = session({ ... });
app.use('/api', sessionMiddleware, apiRouter);
将 Redis 用于会话存储,并在一小时后使每个会话过期。
https://github.com/tj/connect-redis
安装必要的软件包
npm install yarn
yarn add connect-redis, express, express-session, express, uid-safe
应用.js
var connectRedis = require('connect-redis')(expressSession),
uid = require('uid-safe').sync
sessionMiddleware = expressSession({
genid: req => {
if(req.session && req.session.uid)
return req.session.uid + '_' + uid(24)
return uid(24)
},
store: new connectRedis({
port: 6379,
ttl: 3600 // 1 hour
}),
secret: 'sfsa487838787we',
name: 'live_session',
rolling: true,
saveUninitialized: false,
resave: false,
proxy: true,
logErrors: false,
cookie: {
path: '/',
domain: '.yourdomain.io',
httpOnly: true,
secure: 'yourdomain.io',
expires: new Date(Date.now() + 3600000),
maxAge: 3600000
}