项目权限控制实现
项目背景
本文档详细介绍火山知识库平台的权限控制实现,基于 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