Appearance
NestJS 守卫 (Guards)
NestJS 守卫是一个类,它实现了 CanActivate 接口。守卫的主要职责是根据某些条件(如权限、角色、ACL等)来确定给定请求是否由路由处理程序处理。守卫在中间件之后、管道和拦截器之前执行。
基础概念
守卫解决了应用程序中的授权问题。它们决定请求是否应该被路由处理程序处理,返回 true 允许请求继续,返回 false 则阻止请求继续。
守卫的执行时机:
- 中间件 - 最先执行
- 守卫 - 权限控制
- 拦截器 - 请求/响应转换
- 管道 - 数据验证和转换
- 控制器方法 - 处理请求
守卫接口
每个守卫必须实现 CanActivate 接口:
export interface CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean>;
}
context- 执行上下文,提供对请求对象和其他相关信息的访问
创建基本守卫
让我们创建一个简单的身份验证守卫:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateUser(request.headers.authorization);
}
}
function validateUser(auth: string): boolean {
// 简单的身份验证逻辑
return auth && auth.startsWith('Bearer ');
}
使用守卫
1. 在控制器中使用
import { Controller, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {
@Get()
findAll() {
return 'This route is protected';
}
}
2. 在方法级别使用
@Get(':id')
@UseGuards(AuthGuard)
findOne(@Param('id') id: string) {
return this.catsService.findOne(id);
}
3. 全局注册
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AuthGuard } from './common/guards/auth.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());
await app.listen(3000);
}
bootstrap();
角色守卫
创建一个基于角色的守卫:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
使用 Reflector 来获取角色装饰器:
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// 在控制器中使用
@Post()
@UseGuards(RolesGuard)
@Roles('admin')
createCat(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
执行上下文
执行上下文提供了对请求对象的访问:
@Injectable()
export class PermissionGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const handler = context.getHandler();
const controller = context.getClass();
// 访问请求信息
console.log(request.user);
console.log(handler.name); // 控制器方法名
console.log(controller.name); // 控制器类名
return true;
}
}
异步守卫
守卫可以是异步的:
@Injectable()
export class AsyncAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// 异步验证逻辑
const isValid = await this.validateToken(request.headers.authorization);
if (!isValid) {
throw new UnauthorizedException();
}
return true;
}
private async validateToken(token: string): Promise<boolean> {
// 模拟异步验证
return new Promise(resolve => {
setTimeout(() => {
resolve(token === 'valid-token');
}, 100);
});
}
}
基于资源的守卫
创建一个基于特定资源的守卫:
@Injectable()
export class OwnershipGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user;
const params = request.params;
// 检查用户是否拥有该资源
return user.id === params.userId || user.role === 'admin';
}
}
// 在控制器中使用
@Put(':id')
@UseGuards(OwnershipGuard)
update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
return this.catsService.update(id, updateCatDto);
}
组合守卫
可以同时使用多个守卫:
@Controller('cats')
@UseGuards(AuthGuard, RolesGuard, PermissionGuard)
export class CatsController {
@Get()
findAll() {
return 'Protected route with multiple guards';
}
}
守卫与依赖注入
守卫可以使用依赖注入:
import { Injectable, CanActivate, ExecutionContext, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(@Inject('AUTH_SERVICE') private readonly client: ClientProxy) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')[1];
try {
const user = await this.client.send({ cmd: 'validate_token' }, token);
request.user = user;
return true;
} catch (error) {
return false;
}
}
}
守卫生命周期
守卫的执行顺序和生命周期:
@Injectable()
export class LifecycleGuard implements CanActivate {
constructor() {
console.log('Guard instance created');
}
canActivate(context: ExecutionContext): boolean {
console.log('Guard canActivate method called');
const request = context.switchToHttp().getRequest();
// 守卫逻辑
const canActivate = this.validateRequest(request);
console.log('Guard validation result:', canActivate);
return canActivate;
}
private validateRequest(request: any): boolean {
// 实际验证逻辑
return !!request.headers.authorization;
}
}
守卫最佳实践
1. 错误处理
@Injectable()
export class ErrorHandlingGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
try {
if (!request.headers.authorization) {
throw new UnauthorizedException('Authorization header required');
}
// 其他验证逻辑
return this.validateToken(request.headers.authorization);
} catch (error) {
// 记录错误
console.error('Guard validation failed:', error);
throw error;
}
}
private validateToken(token: string): boolean {
// 验证逻辑
return token.startsWith('Bearer ');
}
}
2. 缓存验证结果
@Injectable()
export class CachedAuthGuard implements CanActivate {
private cache = new Map<string, { valid: boolean; timestamp: number }>();
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
const cached = this.cache.get(token);
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5分钟缓存
return cached.valid;
}
const isValid = await this.validateToken(token);
this.cache.set(token, { valid: isValid, timestamp: Date.now() });
return isValid;
}
private async validateToken(token: string): Promise<boolean> {
// 异步验证逻辑
return token.startsWith('Bearer ');
}
}
3. 守卫工厂
创建一个守卫工厂来生成参数化守卫:
export const createRoleGuard = (requiredRole: string) => {
@Injectable()
class RoleGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return request.user?.role === requiredRole;
}
}
return RoleGuard;
};
// 使用
@Controller('admin')
@UseGuards(createRoleGuard('admin'))
export class AdminController {
// ...
}
与中间件、拦截器的关系
- 中间件 - 处理通用请求逻辑,如日志、CORS
- 守卫 - 处理权限控制和访问验证
- 拦截器 - 处理请求/响应转换、缓存、日志记录
- 管道 - 处理数据验证和转换
总结
NestJS 守卫是控制访问权限的强大工具。它们允许你根据特定条件决定请求是否应该被处理,从而保护你的应用程序免受未授权访问。
守卫的主要特点:
- 实现 CanActivate 接口
- 可以同步或异步执行
- 可以访问执行上下文
- 支持依赖注入
- 可以在不同级别应用(方法、控制器、全局)
- 可以组合使用多个守卫