nestjs实战-登录、鉴权(二)
nestjs实战-登录、鉴权(二)
上一章中介绍了登录鉴权分两步:
- 用户登录过程
- 登录成功后,带token请求业务接口的过程
用户登录过程已经介绍,接下来介绍一下业务流程中的认证过程
一、业务接口token验证过程
业务接口验证流程:
-
登录成功后,用户将token存储到本地缓存,每次发送请求时,需要在header,Authentication:[token] 将token带给后端
-
后端操作:
- 全局守卫
jwt.guard.ts,是否进入jwt策略、白名单校验等 - 触发 JWT 策略:获取jwt、解析、验证等操作
- 成功后进入 控制器、服务、最终返回数据
- 全局守卫
二、先看代码实现:
先看一下目录结构:
![]()
首先需要创建两个文件 jwt.guard.ts,jwt.strategy.ts,后面再介绍文件内的实现;
我们希望所有业务代码都需要进行token验证(提供不走验证逻辑的配置),所以 jwt.guard.ts守卫需要全局注册:
App.module.ts
import { APP_GUARD } from '@nestjs/core';
import { JwtGuard } from './modules/auth/guards/jwt.guard';
@Module({
// ...
providers: [
// 全局注册 jwt 守卫,所有业务接口都会走这个守卫
{ provide: APP_GUARD, useClass: JwtGuard },
],
})
export class AppModule {}
auth.module.ts
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
// ...
providers: [
// ...
JwtStrategy,
],
})
在哪里使用 JwtStrategy 呢?
在代码中只看到 jwt.strategy.ts 作为一个提供者,在 auth.module.ts 中被注册;
提供者正常分三步:定义、注册、[注入|使用](类的构造函数constructor中注入),但是 jwt.strategy.ts 只有定义、注册 两部,因为:
之所以在代码里找不到
JwtStrategy被constructor注入的地方,是因为它的工作方式比较特殊:它是被 NestJS 框架“自动”消费的,而不是被你的业务代码显式注入的
逻辑梳理
我们的登录、注册接口肯定是不需要进行 token 验证的,所以我们这个 jwt 全局守卫还需要一个开关来控制;
还记得我们在之前的章节介绍 拦截器-统一响应数据格式,也有类似的开关控制bypass.decorator.ts 内部逻辑,为类或方法 设置元数据 SetMetadata
此处也是类似的逻辑:
public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
auth.controller.ts
为类 设置上 特定的元数据,后续在 守卫中获取对应的值,作为判断逻辑;
// ...
import { Public } from '~/common/decorators/public.decorator';
@Controller('auth')
@Public() // 类下面所有的路由都不需要检验token
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
) {}
// 注册
@Post()
register(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
// 登录
@Post('login')
login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
}
核心逻辑
Jwt.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { AuthStrategy } from '../auth.constant';
import { IS_PUBLIC_KEY } from '~/common/decorators/public.decorator';
@Injectable()
export class JwtGuard extends AuthGuard(AuthStrategy.JWT) {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
// 获取 类、方法上的 元数据
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
// 不需要校验token,接口可以直接访问,例如:登录、注册、获取验证码 等接口
if (isPublic) {
return true;
}
/**
* super.canActivate(context) 的作用是:调用 @nestjs/passport 里已经实现好的 JWT 认证流程,包括:
* 1. 从请求中提取 token(通常是 Bearer)
* 2. 调用对应 JwtStrategy
* 3. 验证签名、过期时间等
* 4. 验证通过后把结果挂到 request.user
* 5. 最后返回 true(通过)或抛异常(401)
*/
return super.canActivate(context);
}
}
Jwt.strategy.ts
在守卫触发后,通过守卫内部的 super.canActivate(context) 触发执行此策略,注意策略名称需一致;
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthStrategy } from '../auth.constant';
import { securityRegToken, ISecurityConfig } from '~/config';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, AuthStrategy.JWT) {
constructor(
private readonly configService: ConfigService,
) {
const securityConfig = configService.get<ISecurityConfig>(securityRegToken)
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: securityConfig.jwtSecret,
});
}
validate(payload: any) {
console.log('payload', payload);
return payload;
}
}
代码部分说明:
-
PassportStrategy:Nest 里把passport-jwt的Strategy包装成可注入的类。
-
ExtractJwt、Strategy:来自passport-jwt,负责「从请求里拿 JWT」和「验签逻辑」。 -
export class JwtStrategy extends PassportStrategy(Strategy, AuthStrategy.JWT)- 第二个参数
AuthStrategy.JWT是 策略名(一般是'jwt'),要和AuthGuard('jwt')/AuthGuard(AuthStrategy.JWT)一致。 - Nest 会在需要 JWT 认证时,走这个策略。
- 第二个参数
-
构造函数里的
super({...})-
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()只从Authorization: Bearer <token>里取 JWT;没有 Bearer 或格式不对,认证会失败。 -
ignoreExpiration: false过期 token 会被拒绝;若为true则仍可能验签通过(一般不这么配在生产鉴权上)。 -
secretOrKey: securityConfig.jwtSecret和签发 token 时用的密钥必须一致,否则验签失败。
-
-
validate(payload)- JWT 验签、过期检查通过后,
passport-jwt会把解码后的 payload(一般是{ sub, name, ... })传给validate。 - 你这里
return payload,表示request.user就是整个 payload。 - 若你希望
req.user是数据库里的用户对象,通常会在这里根据payload.sub查库,再return user。
- JWT 验签、过期检查通过后,
和请求流程的关系
Guard 触发 JWT 策略 → 从 Header 取 token → 用 jwtSecret 验签 → 调用 validate(payload) → 返回值赋给 request.user → 再进控制器。
三、整个过程的生命周期
当我访问一个业务接口时的执行顺序,例如直接访问 /users 接口:
-
全局 Guard 先执行:
JwtGuard.canActivate() -
JwtGuard内部调用super.canActivate(context)(AuthGuard('jwt')) -
触发 JWT Strategy:
JwtStrategy- 从
Authorization: Bearer xxx提取 token - 验签、校验过期
- 调用
JwtStrategy.validate(payload),并把返回值挂到request.user 以上操作都是库自动帮我们执行的
- 从
-
Guard 通过后,进入 Controller、Service ,最终响应给前端
四、总结
以上就完成熟悉了整个 登录鉴权的过程;
- 熟悉了守卫的实战场景
- 熟悉 鉴权相关的逻辑流程,不管是nestjs 还是其他后端语言,这块逻辑是不变的
- 基于nestjs,熟悉了它的实现流程,各个npm包的作用