You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS CASL权限控制异常:ADMIN可创建SUPER_ADMIN用户

问题分析与解决方案

核心问题

你的代码存在两个关键问题导致ADMIN能创建SUPER_ADMIN:

  1. 权限校验未传入待创建的用户数据:RolesGuard中调用ability.can('create', User)时,仅验证了动作和主体,没有传入请求体里的用户类型信息,CASL无法触发你定义的条件规则,直接返回true。
  2. 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用户时:

  1. 请求体中的userType: SUPER_ADMIN会被传入ability.can('create', User, userToCreate)
  2. CASL会匹配ADMIN的规则,检查userType !== SUPER_ADMIN,返回false,权限校验失败
  3. 只有当待创建用户的类型是允许的范围时,canAccess才会返回true

内容的提问来源于stack exchange,提问作者John Oliver

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 19:05:01