1-5-2 过渡 解析官方示例:LocalStrategy 与 JwtStrategy
本节深度解析 NestJS 官方 JWT 示例中两个核心策略的工作原理和协作关系,梳理从用户登录到 Token 验证的完整请求生命周期。
LocalStrategy 与 JwtStrategy 的定位
官方示例中涉及两种策略,容易混淆。它们是完全独立的认证环节,职责分明:
| 策略 | 触发时机 | 校验内容 | 位置 |
|---|---|---|---|
| LocalStrategy | 用户登录(/auth/login) | 用户名 + 密码 | providers 数组 |
| JwtStrategy | 访问受保护接口(如 /auth/profile) | JWT Token 签名和有效期 | providers 数组 |
两者都在 AuthModule 的
providers中注册,作为 DI 容器管理的实例供 Guard 调用。它们之间没有直接依赖关系。
LocalStrategy:登录认证
LocalStrategy 负责验证用户提交的用户名和密码是否正确:
// auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
typescript
工作流程:
POST /auth/login
│
▼ @UseGuards(AuthGuard('local')) 拦截
│
▼ LocalStrategy.validate(username, password)
│
▼ AuthService.validateUser() → 查询数据库,校验密码
│
├── 校验失败 → throw UnauthorizedException() → 返回 401
│
└── 校验成功 → 返回 user 对象
│
▼ Passport 自动将 user 赋值到 req.user
│
▼ Controller 中的 login 方法执行
│
▼ JwtService.signAsync() 签发 Token → 返回给客户端
text
关键机制:Passport 框架自动将 validate() 返回的值赋给 req.user,无需手动操作。
LocalStrategy 的校验逻辑完全可以写在 AuthService 或 Controller 中,不一定要通过 Guard 机制。官方示例使用 Guard 是为了展示完整的 Passport 集成模式。
JwtStrategy:Token 验证
JwtStrategy 负责验证客户端携带的 JWT Token 是否合法:
// auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
// payload 已经被自动解码和签名验证
return { userId: payload.sub, username: payload.username };
}
}
typescript
JwtStrategy 自动处理的事情(无需开发者关心):
- 从
Authorization: Bearer <token>头部提取 Token - 使用
secret验证签名是否正确 - 检查 Token 是否过期(
ignoreExpiration: false) - 解码 payload 部分,传入
validate()方法
validate() 方法收到的 payload 结构:
// Token 中编码的 payload
{
sub: 1, // 用户 ID(签发时放入)
username: "john", // 用户名(签发时放入)
iat: 1684281600, // 签发时间(自动添加)
exp: 1684285200 // 过期时间(自动添加)
}
typescript
完整请求生命周期
将两个策略组合起来,形成完整的认证流程:
阶段一:登录(签发 Token)
═══════════════════════════════════════
客户端 服务端
│ │
│ POST /auth/login │
│ { username, password } │
│────────────────────────────▶│
│ │ @UseGuards(AuthGuard('local'))
│ │ │
│ │ ▼
│ │ LocalStrategy.validate()
│ │ ├── 查询数据库
│ │ └── 校验密码
│ │ │
│ │ ▼ req.user = user
│ │ Controller.login()
│ │ └── jwtService.signAsync(payload)
│ │ │
│ { access_token: "eyJ..." } │◀────────│
│◀────────────────────────────│
│ │
阶段二:访问受保护资源(验证 Token)
═══════════════════════════════════════
客户端 服务端
│ │
│ GET /auth/profile │
│ Authorization: Bearer eyJ.. │
│────────────────────────────▶│
│ │ @UseGuards(AuthGuard('jwt'))
│ │ │
│ │ JwtStrategy 自动:
│ │ 1. 提取 Bearer Token
│ │ 2. 验证签名(secret)
│ │ 3. 检查过期时间
│ │ 4. 解码 payload
│ │ │
│ │ ▼
│ │ JwtStrategy.validate(payload)
│ │ └── return { userId, username }
│ │ │
│ │ ▼ req.user = { userId, username }
│ │ Controller.getProfile()
│ │ └── return req.user
│ │ │
│ { userId: 1, username: "john" } │
│◀────────────────────────────│
text
Controller 层的完整实现
// auth/auth.controller.ts
import {
Controller,
Get,
Post,
Body,
UseGuards,
Request,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
// 登录接口 — 使用 LocalStrategy 校验用户名密码
@UseGuards(AuthGuard('local'))
@HttpCode(HttpStatus.OK)
@Post('login')
async login(@Request() req) {
// req.user 由 LocalStrategy.validate() 返回
return this.authService.login(req.user);
}
// 受保护接口 — 使用 JwtStrategy 校验 Token
@UseGuards(AuthGuard('jwt'))
@Get('profile')
getProfile(@Request() req) {
// req.user 由 JwtStrategy.validate() 返回
return req.user;
}
}
typescript
AuthModule 完整配置
// auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';
import { jwtConstants } from './constants';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
typescript
AuthGuard 的选择机制
AuthGuard() 接收策略名称作为参数,决定使用哪个 Strategy:
@UseGuards(AuthGuard('local')) // → 触发 LocalStrategy.validate()
@UseGuards(AuthGuard('jwt')) // → 触发 JwtStrategy.validate()
typescript
AuthGuard 本身是一个 @Injectable() 装饰的类,被 DI 容器管理,可以在任何 Controller 中使用。
req.user 的赋值机制
req.user 的值来自对应 Strategy 的 validate() 方法返回值:
// LocalStrategy.validate() 返回值 → 登录时的 req.user
async validate(username: string, password: string) {
const user = await this.authService.validateUser(username, password);
return user; // ← 这个返回值就是 req.user
}
// JwtStrategy.validate() 返回值 → 受保护接口的 req.user
async validate(payload: any) {
return { userId: payload.sub, username: payload.username }; // ← req.user
}
typescript
本节要点
- 两种策略各自独立:LocalStrategy 处理登录校验,JwtStrategy 处理 Token 验证,互不干扰
- providers 注册:两种策略都在 AuthModule 的
providers中注册,由 DI 容器管理 - AuthGuard 路由选择:通过
AuthGuard('local')或AuthGuard('jwt')指定使用哪个策略 - req.user 自动赋值:Passport 框架自动将
validate()返回值赋给req.user - Token 验证自动化:JwtStrategy 内部自动处理 Token 提取、签名验证、过期检查,
validate()只需处理解码后的 payload
↑