Skip to content
第 174 / 250 章Node⏱ 10 分钟阅读

第 174 章:守卫(Guard)与权限控制

学习目标

  • 理解 Guard 的作用
  • 学会自定义 Guard
  • 掌握 RBAC 权限控制
  • 学会组合多个 Guard

一、什么是 Guard

Guard 用于判断请求是否应该被处理,通常用于鉴权、角色检查。

二、内置 Guard

typescript
import { AuthGuard } from '@nestjs/passport';

@Controller('users')
@UseGuards(AuthGuard('jwt'))   // 内置 JWT Guard
export class UserController {}

三、自定义 Guard

3.1 实现 CanActivate

typescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return request.headers.authorization !== undefined;
  }
}

3.2 完整示例:API Key 校验

typescript
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const apiKey = request.headers['x-api-key'];

    if (!apiKey || apiKey !== process.env.API_KEY) {
      throw new UnauthorizedException('Invalid API key');
    }
    return true;
  }
}

// 使用
@UseGuards(ApiKeyGuard)
@Controller('protected')
export class ProtectedController {}

四、基于角色(RBAC)

4.1 自定义装饰器

typescript
import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

4.2 角色 Guard

typescript
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } 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_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles || requiredRoles.length === 0) {
      return true;   // 无角色要求,放行
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;   // 由 JWT 策略设置

    if (!user || !requiredRoles.includes(user.role)) {
      throw new ForbiddenException('权限不足');
    }
    return true;
  }
}

4.3 使用

typescript
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)   // 多个 Guard
export class AdminController {
  @Get()
  @Roles('admin')      // 仅 admin 可访问
  getDashboard() {
    return { msg: 'Admin dashboard' };
  }

  @Get('users')
  @Roles('admin', 'manager')   // admin 或 manager
  listUsers() {
    return this.userService.findAll();
  }
}

五、JWT 实战

5.1 安装

bash
pnpm add @nestjs/passport passport passport-jwt @nestjs/jwt
pnpm add -D @types/passport-jwt

5.2 JWT 策略

typescript
// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET!,
    });
  }

  // 验证通过后,把返回值挂到 request.user
  async validate(payload: any) {
    return {
      userId: payload.sub,
      username: payload.username,
      role: payload.role,
    };
  }
}

5.3 Auth 模块

typescript
@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: { expiresIn: '7d' },
    }),
  ],
  providers: [JwtStrategy],
  exports: [JwtModule],
})
export class AuthModule {}

5.4 Auth Service

typescript
@Injectable()
export class AuthService {
  constructor(
    private jwtService: JwtService,
    private userService: UserService,
  ) {}

  async login(dto: LoginDto) {
    const user = await this.userService.findByEmail(dto.email);
    if (!user || !await bcrypt.compare(dto.password, user.password)) {
      throw new UnauthorizedException('账号或密码错误');
    }

    const payload = {
      sub: user.id,
      username: user.name,
      role: user.role,
    };

    return {
      access_token: this.jwtService.sign(payload),
      user: { id: user.id, name: user.name, role: user.role },
    };
  }
}

5.5 JwtAuthGuard

typescript
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

5.6 路由使用

typescript
@Controller('profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@Req() req: Request) {
    // req.user 由 JWT Strategy 注入
    return req.user;
  }
}

六、@Public 装饰器(跳过鉴权)

typescript
import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

// 全局 JwtAuthGuard 中识别
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) return true;
    return super.canActivate(context);
  }
}

// 使用
@Controller('auth')
export class AuthController {
  @Public()
  @Post('login')
  login(@Body() dto: LoginDto) {
    return this.authService.login(dto);
  }
}

七、全局 Guard

typescript
// main.ts
import { APP_GUARD } from '@nestjs/core';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalGuards(new JwtAuthGuard());   // 全局生效
  await app.listen(3000);
}
typescript
// 或在模块中全局注册(推荐,可注入依赖)
@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
  ],
})
export class AppModule {}

八、Throttler(限流)

8.1 安装

bash
pnpm add @nestjs/throttler

8.2 配置

typescript
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot([{
      ttl: 60000,    // 1 分钟
      limit: 10,     // 最多 10 次
    }]),
  ],
  providers: [
    { provide: APP_GUARD, useClass: ThrottlerGuard },
  ],
})
export class AppModule {}

// 局部覆盖
@SkipThrottle()
@Get('health')
health() {
  return { ok: true };
}

@Throttle({ default: { limit: 3, ttl: 60000 } })
@Post('login')
login() {}

九、组合 Guard

typescript
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)   // 顺序执行
export class AdminController {
  @Get('dashboard')
  @Roles('admin')
  dashboard(@CurrentUser() user: User) {
    // 1. 先验证 JWT
    // 2. 再检查角色
    return { user };
  }
}

十、本章小结

概念关键
CanActivateGuard 接口
SetMetadata自定义元数据
Reflector读取元数据
JwtAuthGuardJWT 验证
RolesGuard角色检查
@Public跳过验证
Throttler限流
APP_GUARD全局注册

动手练习

  1. 写一个简单的 API Key Guard
  2. 用 JWT 实现登录,获取当前用户信息
  3. 实现 RBAC,只有 admin 能访问 /admin/*
  4. 用 @Public 让登录接口跳过 JWT 校验

推荐阅读


下一章:第 175 章:拦截器(Interceptor)

本站基于 VitePress 构建 · 由 Codebook 团队维护