需求
毋庸置疑,登录 是一个系统权限机制设计最复杂的地方。一个系统权限做得是否健壮,用户登录时系统所完成的事情起决定作用。
我们现在的系统登录的需求如下:
- Express 的 Session 机制使用 MongoDB 实现存储。
- 支持单点登录:X 帐号(用户)在 A 终端登录的情况下,如果该帐号要在 B 终端登录,第一次登录动作,系统会返回一条信息,告知 X 帐号(用户) A 帐号已在别处登录,要求用户确认登录,如果用户确认继续,则 X 帐号(用户)会在 B 终端登录,A 终端处 X 的有效 Session会被清除。
技术路线
一旦用户提交登录请求,系统收到后都会将当前 Session 置为无效。
判断欲登录帐号是否已经是登录状态,如果是,肯定不是当前终端(当前 Session 已销毁),返回并告知用户该帐号已在别处登录,反之,直接登录。
-
前端收到回复,如果被告知该帐号已在别处登录,是否继续,若是,则服务器收到“直接登录”的请求后,直接登录即可。
可能存在的问题:
- 任何人都可以在不校验用户合法性的情况下得知其他任何人当前的登录状态,因为用户的合法性校验是在“直接登录”阶段做的。
- 用户是否“直接登录”是由前端传值来作判断的,如果用户直接传 “直接登录”,就可以省去“二次确认阶段”,而直接登录,不过总之是否需要“二次确认登录”也是由当前用户无鉴权地操作的,所以感觉从系统的健壮性方面考虑,不足为惧。
具体实现
用户登录时:
req.session.regenerate(() => {
req.session.save(() => {
if (paraLoginType === '2') {
directLogin(paraUid, paraLoginPass, paraLoginHospitalCode, req, res);
} else if (paraLoginType === '1') {
sessionModel.findOne({
'session.uid': paraUid
}, (err, result) => {
if (err) {
handleError(err);
} else {
if (result) {
// returnToTellUsersToDoThe2ndLogin
res.json({
respData: {
status: '2',
username: 'conform a 2nd login'
},
respResultset: [],
respCode: '200'
});
} else {
directLogin(paraUid, paraLoginPass, paraLoginHospitalCode, req, res);
}
}
});
} else {
handleError('paraLoginType Error!');
}
});
});
- 如果前端传来的参数
paraLoginType
是2
则直接登录。 - 否则,在
Session
中查找uid
是当前欲登录的 uid,如果没找到,则直接登录。 - 否则,返回前端,通知用户是否确认二次登录。
<font size="9999">坑</font>
在 req.session.regenerate
后,并不能通过 Mongoose 的 Model 获取到应查到的数据,但是如果不在 Express 中使用 Model 去获取,是可以获取到的,这就让我很困惑,试了好多方法,看了好多 Github 的 issues,查了好多文档,读了好多源码,也没有解决。
最后偶然机会在 req.session.regenerate
后再调用 req.session.save
后,就可以正常通过 Mongoose 的 Model 获取到应该的数据了。个人猜测是因为 req.session.regenerate
出于速度的考量只是将重新生成的数据放在内存中,并没有写回,所以导致 Model 查不到,时间紧迫,时候再通过看源码证实。
参考资料
- express-session使用理解
- node.js中express-session配置项详解
- express-session
- I am facing an issue where session regenerated in but document is nil in express, connect-redis, ioredis #258
- Use of resave and saveUninitialized configuration options #273
- NodeJS Express中无法获取到存储到session中的值
- Mongoose official document
- Mongoose 基本功能使用
- expressjs/session on Github
- Mongoose 模型提供了 find, findOne, 和 findById 方法用于文档查询。
- Node.js MongoDB Driver API
- mongoose中connect()、createConnection()和connection的区别和作用
- 【大雾】mongoose中createConnection和connect的大坑!
- node的express中间件之session
- jdesboeufs/connect-mongo on Github