NestJS CASL权限控制异常:ADMIN可创建SUPER_ADMIN用户
问题分析与解决方案
核心问题
你的代码存在两个关键问题导致ADMIN能创建SUPER_ADMIN:
- 权限校验未传入待创建的用户数据:RolesGuard中调用
ability.can('create', User)时,仅验证了动作和主体,没有传入请求体里的用户类型信息,CASL无法触发你定义的条件规则,直接返回true。 - CASL规则逻辑存在冗余与优先级问题:
cannot和can的组合写法容易出现冲突,且当前规则仅限定了允许创建的两种类型,未明确覆盖所有合法场景,同时规则顺序可能导致校验失效。
修复步骤
1. 修改RolesGuard,传入待创建用户数据进行校验
在canActivate方法中,获取请求体的用户数据,将其传入CASL的can方法,让权限系统能基于实际要创建的用户类型做判断:
async canActivate(context: ExecutionContext): Promise<boolean> { const requiredRoles = this.reflector.get<USER_TYPE[]>('roles', context.getHandler()); if (!requiredRoles) return true; const request = context.switchToHttp().getRequest(); const bearerToken = request.headers.authorization; if (!bearerToken || !bearerToken.startsWith('Bearer ')) return false; const token = bearerToken.split(' ')[1]; try { const decoded = this.jwtService.verifyAccessToken(token); const userRole = decoded.userType; const ability = this.userAbilityFactory.createForUser(userRole); // 获取请求体中的待创建用户数据,传入can方法进行条件校验 const userToCreate = request.body; const canAccess = ability.can('create', User, userToCreate); if (canAccess) { request.user = decoded; const userId = decoded.userId; const loggedIn = await this.redisService.getValue(userId); if (!loggedIn) return false; return true; } return false; } catch (error) { return false; } }
2. 优化UserAbilityFactory的权限规则
简化ADMIN的权限规则,直接通过can的条件排除SUPER_ADMIN,避免cannot和can的冲突问题:
@Injectable() export class UserAbilityFactory { createForUser(userType: USER_TYPE): MongoAbility { try { const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility); if (userType === USER_TYPE.SUPER_ADMIN) { can('manage', 'all'); } if (userType === USER_TYPE.ADMIN) { // 允许创建除SUPER_ADMIN外的所有用户类型 can('create', User, { userType: { $ne: USER_TYPE.SUPER_ADMIN, }, }); // 如果需要仅允许特定类型,也可以用$in: // can('create', User, { // userType: { // $in: [USER_TYPE.COMPANY_ADMIN, USER_TYPE.STORE_ADMIN, USER_TYPE.MANAGER], // }, // }); } return build(); } catch (error) { throw error; } } }
注意:要确保User实体的userType字段和请求体中的userType字段名称一致,否则CASL无法匹配条件。
3. 额外建议:拆分角色守卫与CASL守卫
当前RolesGuard同时承担了角色校验和CASL权限校验的职责,建议拆分出单独的CaslGuard,让职责更清晰,便于后续维护:
- RolesGuard仅负责校验用户角色是否在
@Roles()装饰器指定的列表中 - CaslGuard负责基于CASL规则校验具体的动作权限
验证逻辑
修复后,当ADMIN尝试创建SUPER_ADMIN用户时:
- 请求体中的
userType: SUPER_ADMIN会被传入ability.can('create', User, userToCreate) - CASL会匹配ADMIN的规则,检查
userType !== SUPER_ADMIN,返回false,权限校验失败 - 只有当待创建用户的类型是允许的范围时,
canAccess才会返回true
内容的提问来源于stack exchange,提问作者John Oliver
相关产品推荐
相关产品推荐

