NestJS 中生成、校验和使用 Token

191 阅读2分钟

为什么需要 Token?

客户端是无状态的,正常情况下无法识别是哪个用户发送的请求。传统做法是使用 Cookie 或 Session,但它们各有缺点:

  • Cookie:存储在客户端,每次发送请求会自动携带,但容易触发 CSRF 攻击,且存在跨域问题。
  • Session:存储在服务端,通过 Cookie 存储用户 ID 等非敏感信息,服务端生成用户 ID - 用户详情的映射表,根据每次请求的 Cookie 找到对应的用户。缺点是对服务端压力大,且容易丢失。

Token:存储在客户端,解决了上述问题。

Token 流程

  1. 客户端发送用户名和密码进行登录
  2. 服务端校验用户发送的数据是否正确
  3. 如果正确,生成一个 Token 并返回
  4. 后续客户端请求需携带 Token
  5. 服务端验证 Token,成功后返回数据

Token 流程图

生成 Token

使用 JWT

什么是 JWT?

JWT(JSON Web Token)是一个开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。JWT 是一个很长的字符串,中间使用 . 分隔为三部分:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

写成一行,就是下面的样子:

Header.Payload.Signature

JWT 结构

在 NestJS 中使用 JWT

  1. 安装 JWT 包

    npm install @nestjs/jwt
    
  2. 配置 JWT 模块

    user.module.ts 中配置 JWT 模块:

    import { Module } from '@nestjs/common';
    import { JwtModule } from '@nestjs/jwt';
    import { UsersService } from './users.service';
    import { UsersController } from './users.controller';
    import { jwtSecret } from './config';
    
     @Module({
         imports: [
           JwtModule.register({
             secret: jwtSecret,
             signOptions: { expiresIn: '1d' },
           }),
         ],
         providers: [UsersService],
         controllers: [UsersController],
       })
    export class UsersModule {}
    
  3. 生成 Token

    users.service.ts 中生成 Token:

    import { Injectable } from '@nestjs/common';
    import { JwtService } from '@nestjs/jwt';
    import { User } from './interfaces/user.interface';
    
    @Injectable()
    export class UsersService {
      private readonly users: User[] = [
        { userId: 1, username: 'zs', password: '123' },
        { userId: 2, username: 'lisi', password: '456' },
      ];
    
      constructor(private readonly jwtService: JwtService) {}
    
      findUserByName(username: string, password: string): User | undefined {
        const user = this.users.find(u => u.username === username && u.password === password);
        if (user) {
          const payload = { userId: user.userId, username: user.username };
          const token = this.jwtService.sign(payload);
          return { ...user, token };
        }
        return undefined;
      }
    }
    
  4. 控制器中使用

    import { Controller, Get, Query } from '@nestjs/common';
    import { UsersService } from './users.service';
    
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
    
      @Get()
      login(@Query('username') username: string, @Query('password') password: string) {
        return this.usersService.findUserByName(username, password);
      }
    }
    
  5. 发送请求

    发送请求 http://127.0.0.1:3000/users?username=zs&password=123,客户端将收到一个 Token。

image.png

校验 Token

使用 Guard 进行前置校验

  1. 生成 Guard 文件

    nest g guard auth --no-spec
    
  2. 实现 Guard

    import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
    import { Request } from 'express';
    import { JwtService } from '@nestjs/jwt';
    
    @Injectable()
    export class AuthGuard implements CanActivate {
      constructor(private readonly jwtService: JwtService) {}
    
      async canActivate(context: ExecutionContext): Promise<boolean> {
        const request = context.switchToHttp().getRequest<Request>();
        const token = request.headers.authorization?.split(' ')[1];
    
        if (!token) {
          return false;
        }
    
        try {
          const payload = await this.jwtService.verifyAsync(token);
          request.user = payload;
          return true;
        } catch {
          return false;
        }
      }
    }
    
  3. 控制器中使用 Guard

    import { Controller, Get, UseGuards, Request } from '@nestjs/common';
    import { AuthGuard } from '../auth.guard';
    
    @Controller('users')
    export class UsersController {
      constructor(private readonly usersService: UsersService) {}
    
      @UseGuards(AuthGuard)
      @Get('profile')
      getProfile(@Request() req) {
        return req.user;
      }
    }
    
  4. 发送请求

    发送请求 http://127.0.0.1:3000/users/profile,并在请求头中添加 Authorization: Bearer <token>

    • 未加 Token

      未加 Token

    • 加上 Token

      加上 Token

通过以上步骤,你可以在 NestJS 中生成、校验和使用 Token,确保应用程序的安全性和可靠性。


希望这篇文章对你有所帮助!如果有任何问题或需要进一步的帮助,请随时提问。