跳到主要内容

创建日期:2026-09-08 | 最近更新:2026-09-08 基于 NestJS 12 本机实测:下方 401/200 返回均为真实 curl 输出(自定义 AuthGuard);JWT/Passport 写法以 @nestjs/jwt@nestjs/passport 官方文档为准。

鉴权:Guard / JWT / RBAC

一句话:Nest 里「能不能访问这个接口」由 Guard 决定——它在你设计的中间件之后、controller 之前执行,返回 true 放行、抛异常拒绝。Guard 能读元数据,所以它能实现「这个路由要 admin 角色」这种声明式权限控制,而不是在每个方法里手写 if。

1. 先搞清请求处理顺序

请求 → 中间件(middleware) → 守卫(guard) → 拦截器(interceptor,before) → 管道(pipe) → 处理器(handler)

真正到 controller 之前,鉴权已经过了

Guard 负责「放不放进来」;验证通过后,怎么把用户信息传给 handler,通常靠 @Req() 或自定义装饰器。

2. 手写第一个 Guard(本机实测)

// common/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import type { Request } from 'express';

@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest<Request>();
if (req.headers['x-token'] === 'nest-secret') return true; // 示意:真项目验 JWT
throw new UnauthorizedException('缺少或错误的 x-token');
}
}

用法:路由/控制器上挂 @UseGuards(AuthGuard)

@Get('protected')
@UseGuards(AuthGuard)
protectedData() { return { secret: 'top-secret-data' }; }

实测输出(Nest 12,真实 curl):

⑤ GET /protected 不带 token
→ 401 {"message":"缺少或错误的 x-token","error":"Unauthorized","statusCode":401}

⑥ GET /protected 带 x-token: nest-secret
→ 200 {"secret":"top-secret-data"}

注意 ⑤:根本没进 controller——canActivate 抛的 UnauthorizedException 直接由全局异常过滤器转成了 401。鉴权失败就是「进不了门」,而不是在业务代码里补救。

3. 全局 Guard 与依赖注入

想「全应用默认都要登录」:

// app.module.ts
providers: [
{ provide: APP_GUARD, useClass: AuthGuard }, // 全局守卫
],

个别接口要公开,在路由上加 @Public()(自己用 SetMetadata 定义)+ 守卫里放行白名单。Guard 是普通的 provider,能注入别的 provider(比如 JwtServiceUsersService),所以「读 DB 验证用户」也自然。

4. 元数据驱动的 RBAC:Roles 装饰器 + Reflector

「某些接口只有 admin 能调」的正确姿势是用元数据声明,别在 handler 里写死角色判断:

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// roles.guard.ts —— 用 Reflector 读「这个路由声明了哪些角色」
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator.js';

@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(), // 方法级元数据
context.getClass(), // 类级元数据(兜底)
]);
if (!roles) return true; // 没标 Roles → 不设限
const { user } = context.switchToHttp().getRequest();
if (roles.some((r) => user?.roles?.includes(r))) return true;
throw new ForbiddenException('权限不足');
}
}
@Post('admin')
@Roles('admin') // 声明式:只有 admin 能调
adminThing() {}

这套「@Roles 声明 + Guard 读取 + 处理器里 @Req().user」就是 Nest 生态里 RBAC 的标准形态。配合 Passport/JWT,user 通常是「鉴权守卫验完 token 后塞进 request」的登录用户。

5. JWT 实战(认证 + 鉴权两步)

登录:签发 token

import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
constructor(private readonly jwt: JwtService) {}

login(user: { id: number; roles: string[] }) {
return { accessToken: await this.jwt.signAsync(
{ sub: user.id, roles: user.roles },
{ secret: process.env.JWT_SECRET, expiresIn: '2h' },
)};
}
}

鉴权守卫:验 token → 塞 user

// jwt-auth.guard.ts(把上面 AuthGuard 里硬编码校验换成真验证)
const [type, token] = (req.headers.authorization ?? '').split(' ');
if (type !== 'Bearer' || !token) throw new UnauthorizedException();
try {
req.user = await this.jwt.verifyAsync(token, { secret: process.env.JWT_SECRET });
return true;
} catch {
throw new UnauthorizedException('token 无效或过期');
}

把两段合起来的推荐顺序:

JwtAuthGuard(验 token,塞 req.user)

RolesGuard(读 @Roles 元数据 + req.user.roles 判权限)

两者都用 APP_GUARD 或叠加 @UseGuards(JwtAuthGuard, RolesGuard) 都行。

也可用 @nestjs/passport + passport-jwt 的 Strategy 写法(官方示例居多);本质一样:Strategy 负责「验什么、user 长什么样」,Guard 触发它。两种选一种即可,别混。

别踩的坑

  • secret 别写死进代码:放 process.env.JWT_SECRET(配合 @nestjs/config),生产轮换要平滑;
  • token 一定要有过期时间expiresIn,配合刷新 token 方案;
  • 存哪:Web 端 不要塞 localStorage 裸用,至少 HttpOnly cookie / 内存 + 刷新策略(前端安全另论);
  • 角色别只信前端传来,角色从服务端查/由 token 声明,Guard 里以服务端为准。

6. 小结

想实现用什么
某些接口需要登录自定义 AuthGuard + @UseGuardsAPP_GUARD 全局
声明式角色控制(RBAC)@Roles('admin') 元数据 + RolesGuard(Reflector)
签发/验证 JWT@nestjs/jwtsignAsync/verifyAsync(或 passport-jwt)
有些路由公开@Public() 元数据 + 守卫放行白名单
登录用户怎么传给 handlerGuard 里 req.user = …,再用 @Req()/自定义装饰器取

关联