Skip to content

项目权限控制实现

项目背景

本文档详细介绍火山知识库平台的权限控制实现,基于 NestJS + @casl/ability 构建的混合权限模型(RBAC + ABAC)。

系统架构

核心组件

1. 用户角色定义

typescript
// apps/server/src/modules/auth/constants/roles.constant.ts
export const USER_ROLES = {
  SUPER_ADMIN: 'super_admin',      // 超级管理员
  ADMIN: 'admin',                  // 管理员  
  TEAM_DEVELOPER: 'team_developer', // 团队开发者
  VISITOR: 'visitor'               // 访客
} as const;

export type UserRole = typeof USER_ROLES[keyof typeof USER_ROLES];

2. 权限配置

typescript
// 角色权限映射
export const ROLE_PERMISSIONS = {
  [USER_ROLES.SUPER_ADMIN]: [
    {
      action: Action.MANAGE,
      subject: Subject.ALL,
      reason: '超级管理员拥有所有权限'
    }
  ],
  
  [USER_ROLES.ADMIN]: [
    {
      action: Action.MANAGE,
      subject: Subject.USER,
      reason: '管理员可以管理用户'
    },
    {
      action: Action.MANAGE,
      subject: Subject.ROLE,
      reason: '管理员可以管理角色'
    },
    {
      action: Action.MANAGE,
      subject: Subject.KNOWLEDGE_BASE,
      reason: '管理员可以管理知识库'
    }
  ],
  
  [USER_ROLES.TEAM_DEVELOPER]: [
    {
      action: Action.MANAGE,
      subject: Subject.DOCUMENT,
      reason: '团队开发者可以管理文档'
    },
    {
      action: Action.READ,
      subject: Subject.USER,
      reason: '团队开发者可以查看用户信息'
    }
  ],
  
  [USER_ROLES.VISITOR]: [
    {
      action: Action.READ,
      subject: Subject.DOCUMENT,
      reason: '访客可以查看文档'
    },
    {
      action: Action.READ,
      subject: Subject.KNOWLEDGE_BASE,
      reason: '访客可以查看知识库'
    }
  ]
} as const;

权限检查流程

核心代码实现

1. CASL 权限工厂

typescript
// apps/server/src/modules/auth/casl/casl-ability.factory.ts
@Injectable()
export class CaslAbilityFactory {
  
  /**
   * 为用户创建权限实例
   */
  createForUser(user: User): AppAbility {
    const { can, cannot, build } = new AbilityBuilder<AppAbility>(
      Ability as AbilityClass<AppAbility>
    );

    // 第一层:基于角色的基础权限
    this.applyRolePermissions(user, can, cannot);
    
    // 第二层:基于属性的条件权限
    this.applyAttributePermissions(user, can, cannot);
    
    // 第三层:通用规则
    this.applyUniversalRules(user, can, cannot);

    return build({
      detectSubjectType: (item) => 
        item.constructor as ExtractSubjectType<Subjects>,
    });
  }

  /**
   * 应用角色权限
   */
  private applyRolePermissions(user: User, can: any, cannot: any) {
    const userRoles = user.roles?.map(role => role.name) || [];
    
    if (userRoles.includes('super_admin')) {
      can('manage', 'all');
    } else if (userRoles.includes('admin')) {
      can('manage', ['user', 'role', 'knowledge_base', 'document']);
      can('read', 'system');
    } else if (userRoles.includes('team_developer')) {
      can('manage', 'document', { team: user.team });
      can('read', 'user');
    } else if (userRoles.includes('visitor')) {
      can('read', ['document', 'knowledge_base'], { isPublic: true });
    }
  }

  /**
   * 应用属性权限
   */
  private applyAttributePermissions(user: User, can: any, cannot: any) {
    // 用户可以管理自己的资源
    can('update', 'user', { id: user.id });
    can('manage', 'document', { createdBy: user.id });
    
    // 基于用户等级的权限
    if (user.level >= 5) {
      can('moderate', 'comment');
      can('review', 'document');
    }
    
    // 基于团队的权限
    if (user.team) {
      can('read', 'team_resource', { team: user.team });
    }
  }

  /**
   * 应用通用规则
   */
  private applyUniversalRules(user: User, can: any, cannot: any) {
    // 所有用户都可以查看公开内容
    can('read', 'document', { isPublic: true });
    can('read', 'knowledge_base', { isPublic: true });
    
    // 禁用用户无法执行任何操作
    if (!user.isActive) {
      cannot('manage', 'all');
    }
  }
}

2. 权限装饰器

typescript
// apps/server/src/core/decorators/require-permission.decorator.ts
export const RequirePermission = (action: Action, subject: Subject) =>
  CheckPolicies((ability: AppAbility) => ability.can(action, subject));

// 使用示例
@Controller('users')
export class UserController {
  
  @Get()
  @RequirePermission(Action.READ, Subject.USER)
  async findAll() {
    return this.userService.findAll();
  }
  
  @Post()
  @RequirePermission(Action.CREATE, Subject.USER)
  async create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }
  
  @Delete(':id')
  @RequirePermission(Action.DELETE, Subject.USER)
  async remove(@Param('id') id: string) {
    return this.userService.remove(id);
  }
}

3. 权限守卫

typescript
// apps/server/src/modules/auth/guards/permissions.guard.ts
@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private caslAbilityFactory: CaslAbilityFactory,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const policyHandlers = this.reflector.get<PolicyHandler[]>(
      CHECK_POLICIES_KEY,
      context.getHandler(),
    ) || [];

    if (policyHandlers.length === 0) {
      return true; // 没有权限要求,直接通过
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user as User;

    if (!user) {
      throw new ForbiddenException('用户未认证,请先登录');
    }

    if (!user.isActive) {
      throw new ForbiddenException('用户已被禁用');
    }

    // 创建用户权限实例
    const ability = this.caslAbilityFactory.createForUser(user);

    // 检查所有权限处理器
    const hasPermission = policyHandlers.every((handler) =>
      this.execPolicyHandler(handler, ability),
    );

    if (!hasPermission) {
      throw new ForbiddenException('权限不足,无法执行此操作');
    }

    return hasPermission;
  }

  private execPolicyHandler(handler: PolicyHandler, ability: any) {
    if (typeof handler === 'function') {
      return handler(ability);
    }
    return handler.handle(ability);
  }
}

实际应用场景

场景1:文档管理

权限矩阵:

  • 张三(team_developer):✅ 自己创建的文档,❌ 他人私有文档,✅ 公开文档
  • 李四(admin):✅ 所有文档(管理员权限)
  • 王五(visitor):❌ 私有文档,✅ 公开文档

场景2:知识库访问控制

typescript
// 知识库服务中的权限检查
@Injectable()
export class KnowledgeBaseService {
  
  async findOne(id: string, user: User): Promise<KnowledgeBase> {
    const knowledgeBase = await this.repository.findOne(id);
    
    if (!knowledgeBase) {
      throw new NotFoundException('知识库不存在');
    }
    
    // 使用 CASL 检查权限
    const ability = this.caslAbilityFactory.createForUser(user);
    
    if (!ability.can('read', knowledgeBase)) {
      throw new ForbiddenException('无权访问此知识库');
    }
    
    return knowledgeBase;
  }
  
  async update(
    id: string, 
    updateDto: UpdateKnowledgeBaseDto, 
    user: User
  ): Promise<KnowledgeBase> {
    const knowledgeBase = await this.findOne(id, user);
    
    const ability = this.caslAbilityFactory.createForUser(user);
    
    if (!ability.can('update', knowledgeBase)) {
      throw new ForbiddenException('无权编辑此知识库');
    }
    
    return this.repository.save({ ...knowledgeBase, ...updateDto });
  }
}

高级功能

1. 动态权限检查

typescript
// 支持动态条件的权限检查
@Get('documents/:id')
async getDocument(
  @Param('id') id: string,
  @CurrentUser() user: User
) {
  const document = await this.documentService.findOne(id);
  const ability = this.caslAbilityFactory.createForUser(user);
  
  // 动态检查文档权限
  if (!ability.can('read', document)) {
    throw new ForbiddenException('无权访问此文档');
  }
  
  return document;
}

2. 字段级权限控制

typescript
// 基于权限的字段过滤
@Injectable()
export class UserService {
  
  async findOne(id: string, currentUser: User): Promise<Partial<User>> {
    const user = await this.repository.findOne(id);
    const ability = this.caslAbilityFactory.createForUser(currentUser);
    
    // 根据权限过滤字段
    const filteredUser: Partial<User> = {
      id: user.id,
      username: user.username,
    };
    
    if (ability.can('read', user, 'email')) {
      filteredUser.email = user.email;
    }
    
    if (ability.can('read', user, 'roles')) {
      filteredUser.roles = user.roles;
    }
    
    return filteredUser;
  }
}

3. 批量权限检查

typescript
// 批量检查权限,提高性能
@Injectable()
export class DocumentService {
  
  async findAllAccessible(user: User): Promise<Document[]> {
    const allDocuments = await this.repository.find();
    const ability = this.caslAbilityFactory.createForUser(user);
    
    // 批量过滤有权限的文档
    return allDocuments.filter(doc => ability.can('read', doc));
  }
}

性能优化

1. 权限缓存策略

typescript
// 权限缓存实现
@Injectable()
export class CachedCaslAbilityFactory extends CaslAbilityFactory {
  
  constructor(private cacheManager: Cache) {
    super();
  }
  
  async createForUser(user: User): Promise<AppAbility> {
    const cacheKey = `user_permissions_${user.id}_${user.updatedAt}`;
    
    // 尝试从缓存获取
    let ability = await this.cacheManager.get<AppAbility>(cacheKey);
    
    if (!ability) {
      // 缓存未命中,重新构建
      ability = super.createForUser(user);
      
      // 缓存权限对象(5分钟)
      await this.cacheManager.set(cacheKey, ability, 300);
    }
    
    return ability;
  }
}

2. 数据库查询优化

typescript
// 优化用户权限查询
@Injectable()
export class UserRepository {
  
  async findWithPermissions(id: string): Promise<User | null> {
    return this.repository.findOne({
      where: { id },
      relations: ['roles', 'roles.permissions'],
      cache: {
        id: `user_with_permissions_${id}`,
        milliseconds: 300000, // 5分钟缓存
      },
    });
  }
}

部署和监控

1. 权限日志记录

typescript
// 权限检查日志
@Injectable()
export class AuditLogger {
  
  logPermissionCheck(
    user: User,
    action: string,
    resource: string,
    result: boolean,
    reason?: string
  ) {
    this.logger.log({
      event: 'permission_check',
      userId: user.id,
      username: user.username,
      action,
      resource,
      result,
      reason,
      timestamp: new Date(),
    });
  }
}

2. 权限监控指标

最佳实践

1. 权限设计原则

  • 遵循最小权限原则
  • 权限粒度要合理
  • 避免权限冗余
  • 考虑未来扩展

2. 性能优化建议

  • 合理使用缓存
  • 优化数据库查询
  • 避免过度复杂的权限条件
  • 监控权限检查性能

3. 安全注意事项

  • 记录权限操作日志
  • 定期审查权限配置
  • 及时清理无效权限
  • 防止权限提升攻击

相关文档

贡献者

huoshan
huoshan

用知识点燃技术的火山