使用app.set()/app.get()存储用户角色是否存在并发问题?
关于用app.set()/app.get()存储角色在高并发场景下的风险解答
嘿,这个问题提得特别关键——在大规模多并发的服务里,用全局的app对象存储客户端角色绝对会引发数据混乱,甚至直接搞崩权限校验逻辑,我来给你拆解清楚原因,再说说正确的做法:
核心问题:app是全局共享的,根本不适合存请求级数据
Express/Loopback的app实例是整个应用进程的全局对象,所有进来的请求都会共享同一个app。想象一下:
- 客户端X的请求刚把
employeeRole设成admin - 几乎同时,客户端Y的请求把
employeeRole覆盖成了guest - 这时候X的权限校验逻辑去
app.get('employeeRole'),拿到的居然是Y的角色!
这种竞态条件在低并发的测试环境里很难复现,但到了生产环境的高并发场景下,会频繁出现,完全打乱权限逻辑。
正确的姿势:把角色绑定到请求隔离的上下文里
每个HTTP请求都有自己独立的req(请求对象),Loopback里还有ctx(上下文对象),这些都是请求隔离的,不会被其他请求干扰。结合connect-roles和Loopback,推荐这几种方案:
1. 用connect-roles的原生逻辑,把角色挂到req上
connect-roles本身就是基于请求上下文设计的,你应该在中间件里把当前用户的角色挂载到req.user上,然后在校验时从req里取:
// 先写个中间件,把用户角色挂载到req app.use(async (req, res, next) => { // 这里从你的认证机制(比如JWT、session)里拿当前用户 const currentUser = await getCurrentAuthenticatedUser(req); // 从Loopback的Role/RoleMapping里查用户的角色 const roleMappings = await RoleMapping.find({ where: { principalId: currentUser.id }, include: 'role' }).map(rm => rm.role.name); // 把角色挂到req.user上,connect-roles能直接用 req.user = { ...currentUser, roles: userRoles }; next(); }); // 配置connect-roles的权限规则 const userRoles = new ConnectRoles({ failureHandler: (req, res) => res.status(403).send('无权限访问') }); userRoles.use('view-dashboard', (req) => { // 从req.user拿角色,而不是全局的app! return req.user.roles.includes('admin') || req.user.roles.includes('manager'); });
2. 直接用Loopback的上下文获取用户角色
Loopback在处理远程方法或中间件时,会提供Context对象,你可以直接通过它拿到当前认证用户的角色,不用手动存:
// Loopback远程方法示例 DashboardModel.remoteMethod('getStats', { http: { path: '/stats', verb: 'get' }, returns: { arg: 'stats', type: 'object' }, auth: { strategy: 'jwt' } // 开启JWT认证 }); DashboardModel.getStats = async function(ctx) { // 从上下文拿当前用户 const currentUser = ctx.req.user; // 查用户的角色 const roleMappings = await RoleMapping.find({ where: { principalId: currentUser.id }, include: 'role' }); const userRoles = roleMappings.map(rm => rm.role.name); // 权限校验 if (!userRoles.includes('admin') && !userRoles.includes('viewer')) { throw new Error('你没有权限查看统计数据'); } // 后续业务逻辑 return await fetchStatsData(); };
3. 用Session存角色(适合会话式认证)
如果你的应用用express-session这类会话认证,可以把角色存在req.session里——每个用户的session都是独立的,不会串:
// 登录成功后把角色存到session app.post('/login', async (req, res) => { const { username, password } = req.body; const user = await User.findOne({ where: { username } }); if (user && await bcrypt.compare(password, user.password)) { const roles = await getRolesForUser(user.id); // 存到session req.session.user = { id: user.id, roles }; res.send('登录成功'); } else { res.status(401).send('账号密码错误'); } }); // connect-roles校验时从session取 userRoles.use('view-dashboard', (req) => { return req.session.user?.roles.includes('admin') || req.session.user?.roles.includes('manager'); });
最后再敲个警钟
永远不要用全局的app对象存储和单个请求相关的用户数据!这种做法本质上是把全局变量当请求级存储用,在高并发下必然会出现数据污染。一定要把用户角色这类请求专属的数据,放到req、ctx或者session这种请求隔离的容器里,才能保证权限校验的正确性。
内容的提问来源于stack exchange,提问作者smachs
相关产品推荐
相关产品推荐

