6月2日 01:23
NestJS 怎么做认证和授权?JWT、Guards 和 RBAC 实战
NestJS 的认证授权用 Guards(守卫)+ 策略模式实现。认证(Authentication)验证"你是谁",授权(Authorization)验证"你能做什么"。JWT 是最常用的认证方案,RBAC 是最常用的授权模型。
JWT 认证
安装依赖:
bashnpm install @nestjs/jwt @nestjs/passport passport passport-jwt
配置 JWT 策略
typescript// auth/jwt.strategy.ts @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET, }); } async validate(payload: { sub: string; email: string }) { return { id: payload.sub, email: payload.email }; } }
validate 的返回值会挂到 req.user 上。Passport 自动验证 JWT 签名和过期时间,你只需要解析 payload。
登录签发 Token
typescript// auth/auth.service.ts @Injectable() export class AuthService { constructor(private jwtService: JwtService) {} async login(email: string, password: string) { const user = await this.validateUser(email, password); const payload = { sub: user.id, email: user.email }; return { access_token: this.jwtService.sign(payload), }; } }
保护路由
typescript@UseGuards(AuthGuard('jwt')) @Get('profile') getProfile(@Request() req) { return req.user; }
没带 Token 或 Token 过期的请求返回 401。
RBAC 角色授权
认证只解决"你是谁",授权解决"你能做什么"。
typescript// auth/roles.decorator.ts export const Roles = (...roles: string[]) => SetMetadata('roles', roles); // auth/roles.guard.ts @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler()); if (!requiredRoles) return true; const { user } = context.switchToHttp().getRequest(); return requiredRoles.some(role => user.roles?.includes(role)); } }
使用:
typescript@UseGuards(AuthGuard('jwt'), RolesGuard) @Roles('admin') @Delete('users/:id') removeUser() { return '仅管理员可操作'; }
先过 AuthGuard 验证身份(拿到 user),再过 RolesGuard 验证角色。顺序不能反——没有 user 对象就没法检查角色。
密码安全
永远不要明文存密码。用 bcrypt 哈希:
typescriptimport * as bcrypt from 'bcrypt'; // 注册时哈希 const hash = await bcrypt.hash(password, 10); // 登录时验证 const isValid = await bcrypt.compare(inputPassword, hash);
10 是 salt rounds,越大越安全但越慢。10 是当前推荐值,每增加 1 耗时翻倍。
常见安全措施
1. 限流防暴力破解:用 @nestjs/throttler 限制登录接口的请求频率。
typescriptThrottlerModule.forRoot([{ ttl: 60000, limit: 5 }]),
60 秒内同一 IP 最多 5 次登录请求。
2. CORS 配置:只允许可信域名访问。
typescriptapp.enableCors({ origin: ['https://your-app.com'] });
3. Helmet 设置安全头:npm i helmet,app.use(helmet())。自动加上 X-Content-Type-Options、X-Frame-Options 等安全响应头。
4. 输入验证:用 class-validator 的 DTO 验证所有入参,防止注入攻击。
typescriptexport class CreateUserDto { @IsEmail() email: string; @MinLength(8) password: string; }
Token 刷新
JWT 一旦签发无法撤销(这是无状态的设计)。如果用户改了密码或被踢下线,旧的 Token 仍然有效直到过期。
两种解决方式:
- 短过期 + Refresh Token:access_token 15 分钟过期,refresh_token 7 天过期。refresh_token 存数据库可以主动撤销
- Token 黑名单:过期前把 Token 加入 Redis 黑名单,每次请求检查黑名单。牺牲了无状态的优点但更安全