NestJS权限控制 #
权限控制概述 #
权限控制是确定用户是否有权执行特定操作的过程。常见的权限控制模型包括:
- RBAC(基于角色的访问控制)
- ABAC(基于属性的访问控制)
- ACL(访问控制列表)
RBAC实现 #
定义角色和权限 #
typescript
export enum Role {
USER = 'user',
ADMIN = 'admin',
SUPER_ADMIN = 'super_admin',
}
export enum Permission {
USER_READ = 'user:read',
USER_CREATE = 'user:create',
USER_UPDATE = 'user:update',
USER_DELETE = 'user:delete',
POST_READ = 'post:read',
POST_CREATE = 'post:create',
POST_UPDATE = 'post:update',
POST_DELETE = 'post:delete',
}
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
[Role.USER]: [
Permission.USER_READ,
Permission.POST_READ,
Permission.POST_CREATE,
],
[Role.ADMIN]: [
Permission.USER_READ,
Permission.USER_CREATE,
Permission.USER_UPDATE,
Permission.POST_READ,
Permission.POST_CREATE,
Permission.POST_UPDATE,
Permission.POST_DELETE,
],
[Role.SUPER_ADMIN]: Object.values(Permission),
};
角色装饰器 #
typescript
import { SetMetadata } from '@nestjs/common';
import { Role } from './role.enum';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
权限装饰器 #
typescript
import { SetMetadata } from '@nestjs/common';
import { Permission } from './permission.enum';
export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: Permission[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);
角色守卫 #
typescript
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from './role.enum';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
if (!user) {
throw new ForbiddenException('User not authenticated');
}
const hasRole = requiredRoles.some(role => user.role === role);
if (!hasRole) {
throw new ForbiddenException(
`Required roles: ${requiredRoles.join(', ')}`,
);
}
return true;
}
}
权限守卫 #
typescript
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Permission, ROLE_PERMISSIONS } from './permission.enum';
import { PERMISSIONS_KEY } from './permissions.decorator';
import { Role } from './role.enum';
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
PERMISSIONS_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredPermissions) {
return true;
}
const { user } = context.switchToHttp().getRequest();
if (!user) {
throw new ForbiddenException('User not authenticated');
}
const userPermissions = this.getUserPermissions(user.role);
const hasAllPermissions = requiredPermissions.every(permission =>
userPermissions.includes(permission),
);
if (!hasAllPermissions) {
throw new ForbiddenException(
`Required permissions: ${requiredPermissions.join(', ')}`,
);
}
return true;
}
private getUserPermissions(role: Role): Permission[] {
return ROLE_PERMISSIONS[role] || [];
}
}
使用守卫 #
typescript
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from './roles.guard';
import { PermissionsGuard } from './permissions.guard';
import { Roles } from './roles.decorator';
import { RequirePermissions } from './permissions.decorator';
import { Role, Permission } from './enums';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
export class UsersController {
@Get()
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
@RequirePermissions(Permission.USER_READ)
findAll() {
return 'All users';
}
@Post()
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
@RequirePermissions(Permission.USER_CREATE)
create() {
return 'User created';
}
@Delete(':id')
@Roles(Role.SUPER_ADMIN)
@RequirePermissions(Permission.USER_DELETE)
remove() {
return 'User deleted';
}
}
动态权限 #
数据库存储权限 #
typescript
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(() => Role, role => role.users)
roles: Role[];
}
@Entity()
export class Role {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(() => Permission, permission => permission.roles)
permissions: Permission[];
}
@Entity()
export class Permission {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
resource: string;
@Column()
action: string;
}
权限服务 #
typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class PermissionService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async getUserPermissions(userId: number): Promise<string[]> {
const user = await this.userRepository.findOne({
where: { id: userId },
relations: ['roles', 'roles.permissions'],
});
if (!user) return [];
const permissions = new Set<string>();
user.roles.forEach(role => {
role.permissions.forEach(permission => {
permissions.add(permission.name);
});
});
return Array.from(permissions);
}
async hasPermission(userId: number, permission: string): Promise<boolean> {
const permissions = await this.getUserPermissions(userId);
return permissions.includes(permission);
}
}
动态权限守卫 #
typescript
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PermissionService } from './permission.service';
import { PERMISSIONS_KEY } from './permissions.decorator';
@Injectable()
export class DynamicPermissionsGuard implements CanActivate {
constructor(
private reflector: Reflector,
private permissionService: PermissionService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
PERMISSIONS_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredPermissions) {
return true;
}
const { user } = context.switchToHttp().getRequest();
if (!user) {
throw new ForbiddenException('User not authenticated');
}
const userPermissions = await this.permissionService.getUserPermissions(
user.userId,
);
const hasAllPermissions = requiredPermissions.every(permission =>
userPermissions.includes(permission),
);
if (!hasAllPermissions) {
throw new ForbiddenException('Insufficient permissions');
}
return true;
}
}
资源级权限 #
检查资源所有权 #
typescript
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
@Injectable()
export class ResourceOwnerGuard implements CanActivate {
constructor(private postsService: PostsService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const { user, params } = request;
const postId = parseInt(params.id);
const post = await this.postsService.findOne(postId);
if (!post) {
throw new NotFoundException('Post not found');
}
if (post.authorId !== user.userId && user.role !== 'admin') {
throw new ForbiddenException('You do not own this resource');
}
return true;
}
}
使用资源所有权守卫 #
typescript
@Put(':id')
@UseGuards(JwtAuthGuard, ResourceOwnerGuard)
update(
@Param('id') id: string,
@Body() updatePostDto: UpdatePostDto,
) {
return this.postsService.update(+id, updatePostDto);
}
ABAC实现 #
属性装饰器 #
typescript
import { SetMetadata } from '@nestjs/common';
export const POLICY_KEY = 'policy';
export const Policy = (policy: PolicyHandler) =>
SetMetadata(POLICY_KEY, policy);
export type PolicyHandler = (
user: any,
resource: any,
) => boolean | Promise<boolean>;
ABAC守卫 #
typescript
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { POLICY_KEY, PolicyHandler } from './policy.decorator';
@Injectable()
export class PolicyGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandler = this.reflector.getAllAndOverride<PolicyHandler>(
POLICY_KEY,
[context.getHandler(), context.getClass()],
);
if (!policyHandler) {
return true;
}
const request = context.switchToHttp().getRequest();
const { user, params, body } = request;
if (!user) {
throw new ForbiddenException('User not authenticated');
}
const resource = { ...params, ...body };
const canAccess = await policyHandler(user, resource);
if (!canAccess) {
throw new ForbiddenException('Access denied by policy');
}
return true;
}
}
使用ABAC #
typescript
const canUpdatePost: PolicyHandler = (user, resource) => {
return user.role === 'admin' || resource.authorId === user.userId;
};
@Put(':id')
@Policy(canUpdatePost)
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
return this.postsService.update(+id, updatePostDto);
}
CASL集成 #
安装CASL #
bash
npm install @casl/ability
定义能力 #
typescript
import { Ability, AbilityBuilder, AbilityClass, ExtractSubjectType } from '@casl/ability';
import { User } from './entities/user.entity';
import { Post } from './entities/post.entity';
type Actions = 'manage' | 'create' | 'read' | 'update' | 'delete';
type Subjects = 'User' | 'Post' | User | Post | 'all';
export type AppAbility = Ability<[Actions, Subjects]>;
export function createAbilityForUser(user: any) {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(
Ability as AbilityClass<AppAbility>,
);
if (user.role === 'admin') {
can('manage', 'all');
} else {
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: user.id });
can('delete', 'Post', { authorId: user.id });
}
return build();
}
CASL守卫 #
typescript
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Ability, subject } from '@casl/ability';
import { CHECK_ABILITY, RequiredRule } from './ability.decorator';
@Injectable()
export class AbilitiesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const rules =
this.reflector.get<RequiredRule[]>(CHECK_ABILITY, context.getHandler()) ||
[];
const { user } = context.switchToHttp().getRequest();
const ability = createAbilityForUser(user);
const can = rules.every(rule =>
ability.can(rule.action, subject(rule.subject, rule.resource)),
);
if (!can) {
throw new ForbiddenException('Access denied');
}
return true;
}
}
总结 #
本章学习了NestJS权限控制:
- RBAC角色权限控制
- 动态权限管理
- 资源级权限
- ABAC属性权限
- CASL集成
接下来,让我们学习 自定义装饰器。
最后更新:2026-03-28