nestjs实战-登录、鉴权(二)

0 阅读4分钟

nestjs实战-登录、鉴权(二)

上一章中介绍了登录鉴权分两步:

  • 用户登录过程
  • 登录成功后,带token请求业务接口的过程

用户登录过程已经介绍,接下来介绍一下业务流程中的认证过程

一、业务接口token验证过程

业务接口验证流程:

  • 登录成功后,用户将token存储到本地缓存,每次发送请求时,需要在header,Authentication:[token] 将token带给后端

  • 后端操作:

    • 全局守卫 jwt.guard.ts ,是否进入jwt策略、白名单校验等
    • 触发 JWT 策略:获取jwt、解析、验证等操作
    • 成功后进入 控制器、服务、最终返回数据

二、先看代码实现:

先看一下目录结构:

auth-dir.png

首先需要创建两个文件 jwt.guard.tsjwt.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 只有定义、注册 两部,因为:

之所以在代码里找不到 JwtStrategyconstructor 注入的地方,是因为它的工作方式比较特殊:它是被 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-jwtStrategy 包装成可注入的类。
  • ExtractJwtStrategy:来自 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

和请求流程的关系

Guard 触发 JWT 策略 → 从 Header 取 token → 用 jwtSecret 验签 → 调用 validate(payload) → 返回值赋给 request.user → 再进控制器。

三、整个过程的生命周期

当我访问一个业务接口时的执行顺序,例如直接访问 /users 接口:

  1. 全局 Guard 先执行:JwtGuard.canActivate()

  2. JwtGuard 内部调用 super.canActivate(context)AuthGuard('jwt')

  3. 触发 JWT Strategy:JwtStrategy

    • Authorization: Bearer xxx 提取 token
    • 验签、校验过期
    • 调用 JwtStrategy.validate(payload),并把返回值挂到 request.user
    • 以上操作都是库自动帮我们执行的
  4. Guard 通过后,进入 Controller、Service ,最终响应给前端

四、总结

以上就完成熟悉了整个 登录鉴权的过程;

  • 熟悉了守卫的实战场景
  • 熟悉 鉴权相关的逻辑流程,不管是nestjs 还是其他后端语言,这块逻辑是不变的
  • 基于nestjs,熟悉了它的实现流程,各个npm包的作用