第 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-jwt5.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/throttler8.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 };
}
}十、本章小结
| 概念 | 关键 |
|---|---|
| CanActivate | Guard 接口 |
| SetMetadata | 自定义元数据 |
| Reflector | 读取元数据 |
| JwtAuthGuard | JWT 验证 |
| RolesGuard | 角色检查 |
| @Public | 跳过验证 |
| Throttler | 限流 |
| APP_GUARD | 全局注册 |
动手练习
- 写一个简单的 API Key Guard
- 用 JWT 实现登录,获取当前用户信息
- 实现 RBAC,只有 admin 能访问
/admin/* - 用 @Public 让登录接口跳过 JWT 校验
推荐阅读
下一章:第 175 章:拦截器(Interceptor) →