1. 什麼是守衛?
警衛決定是否允許請求繼續。他們跑 在中間件之後,在攔截器和管道之前。主要用例: 授權 — 檢查使用者是否有存取資源的權限。
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return !!request.user; // true = cho phép, false = 403
}
}
2.基於角色的存取控制(RBAC)
自訂 @Roles() 裝飾器
// auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export enum Role {
Admin = 'admin',
Editor = 'editor',
User = 'user',
}
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
RolesGuard實施
// auth/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY, Role } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Lấy roles yêu cầu từ metadata
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(), // Method-level
context.getClass(), // Class-level
]);
// Nếu không có @Roles() → cho phép mọi user
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.includes(user.role);
}
}
使用
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard) // Apply cho toàn controller
export class UsersController {
@Get()
@Roles(Role.Admin, Role.Editor) // Chỉ admin và editor
findAll() { ... }
@Get(':id')
@Roles(Role.Admin) // Chỉ admin
findOne(@Param('id') id: string) { ... }
@Post()
@Roles(Role.Admin)
create(@Body() dto: CreateUserDto) { ... }
@Delete(':id')
@Roles(Role.Admin)
remove(@Param('id') id: string) { ... }
}
3. 基於權限的授權
// Permissions decorator
export enum Permission {
CreateUser = 'user:create',
ReadUser = 'user:read',
UpdateUser = 'user:update',
DeleteUser = 'user:delete',
ManageOrders = 'order:manage',
}
export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: Permission[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);
// Permissions Guard
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const required = this.reflector.getAllAndOverride<Permission[]>(
PERMISSIONS_KEY,
[context.getHandler(), context.getClass()],
);
if (!required) return true;
const { user } = context.switchToHttp().getRequest();
return required.every(perm => user.permissions?.includes(perm));
}
}
// Sử dụng
@RequirePermissions(Permission.DeleteUser)
@Delete(':id')
remove(@Param('id') id: string) { ... }
4. 資源所有者守護
// Chỉ cho phép chủ sở hữu hoặc admin
@Injectable()
export class OwnerGuard implements CanActivate {
constructor(private usersService: UsersService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
const resourceId = request.params.id;
// Admin luôn được quyền
if (user.role === 'admin') return true;
// Kiểm tra ownership
return user.id === resourceId;
}
}
@UseGuards(JwtAuthGuard, OwnerGuard)
@Patch(':id')
updateProfile(@Param('id') id: string, @Body() dto: UpdateProfileDto) {
// Chỉ user sở hữu profile hoặc admin mới cập nhật được
}
5.CASL——高級授權
npm install @casl/ability
// casl/casl-ability.factory.ts
import { AbilityBuilder, createMongoAbility, MongoAbility } from '@casl/ability';
export type AppAbility = MongoAbility;
@Injectable()
export class CaslAbilityFactory {
createForUser(user: User): AppAbility {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
if (user.role === 'admin') {
can('manage', 'all'); // Admin có mọi quyền
} else {
can('read', 'Post');
can('create', 'Post');
can('update', 'Post', { authorId: user.id }); // Chỉ update post của mình
can('delete', 'Post', { authorId: user.id });
can('read', 'User', { id: user.id }); // Chỉ xem profile mình
can('update', 'User', { id: user.id });
cannot('delete', 'User'); // Không được xóa user
}
return build();
}
}
// Abilities Guard
@Injectable()
export class AbilitiesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
canActivate(context: ExecutionContext): boolean {
const rules = this.reflector.get('check-abilities', context.getHandler());
if (!rules) return true;
const { user } = context.switchToHttp().getRequest();
const ability = this.caslAbilityFactory.createForUser(user);
return rules.every(rule => ability.can(rule.action, rule.subject));
}
}
6. 複合Auth裝飾器
// Kết hợp nhiều guards thành 1 decorator gọn
import { applyDecorators, UseGuards } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
Roles(...roles),
UseGuards(JwtAuthGuard, RolesGuard),
);
}
// Sử dụng cực gọn
@Auth(Role.Admin)
@Delete(':id')
remove(@Param('id') id: string) { ... }
@Auth(Role.Admin, Role.Editor)
@Post()
create(@Body() dto: CreatePostDto) { ... }
七、總結
- 衛兵:決定請求是否繼續(canActivate)
- RBAC:透過 @Roles() + RolesGuard 基於角色
- 權限:細粒度的權限字串
- 業主衛隊:資源所有權檢查
- CASL:針對複雜邏輯的基於屬性的存取控制
- 複合裝飾器: @Auth() 聚合多個守衛
下一篇文章將探討 安全最佳實踐 在 NestJS 中。