3-5:JWT

7 阅读2分钟

实现JWT之前,先简单介绍一下什么叫JWT
JWT(JSON Web Token)是一种用于“身份认证”和“信息传递”的令牌格式。
简单说:用户登录成功后,服务器会生成一个 JWT 发给前端。之后前端每次请求接口时,都把这个 JWT 带上。服务器看到 JWT 后,就知道“这个请求是谁发来的”。
JWT 的典型流程是:
用户登录,提交账号密码。服务器验证成功后,生成 JWT。前端保存 JWT,通常放在 cookie 或 localStorage 中。之后前端请求接口时,在请求头里带上 Authorization: Bearer <token>

1.安装依赖

pnpm add --save @nestjs/jwt
这个工具基于jsonwebtoken实现的,而jsonwebtoken基于javascript实现。它的维护频率并不高,且安全性上没有jose那么好,不过使用它的人依然很多。使用它的最新版也没有什么太大问题。

2.导入JwtModule

@Module({
  imports: [JwtModule.register({ secret: 'hard!to-guess_secret' })],
  providers: [...],
})
export class AuthModule {}

我们需要对它进行简单的配置。关于密钥的生成,要么自己写一个脚本去生成,要么找一些网站自动生成。以网站自动生成为例, 来到https://supabase.com/docs/guides/self-hosting/docker网站的Configuring legacy API keys#会自动生成JWT_SCRECT,并且刷新页面密钥也会随之刷新。
将密钥写入.env文件中, 然后写入配置。

imports: [
    UserModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: {
        expiresIn: '1h',
        algorithm: 'HS256',
      },
    }),
  ],

这里回顾一下如何使用dotenv
安装pnpm add dotenv,然后在main.ts中导入import 'dotenv/config'那么在nestJS中的所有地方都可以使用process.env读取环境变量

3.使用

auth.service.ts中注入

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}
}

然后生成签名:

const payload = { sub: user.id, username: user.email };
return {
  access_token: await this.jwtService.signAsync(payload),
};

我们可以同时修改之前的signIn和sigUp逻辑。并且由于这段生成签名的逻辑是一样的,也可以将它封装成一个函数。 完整的逻辑如下:

import {
  ConflictException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
import { User } from 'src/user/entities/user.entity.js';
@Injectable()
export class AuthService {
  constructor(
    private usersService: UserService,
    private readonly jwtService: JwtService,
  ) {}
  async signUp(
    email: string,
    password: string,
  ): Promise<{ access_token: string }> {
    const user = await this.usersService.findOne(email);
    if (user) {
      throw new ConflictException('User already exists');
    }
    //hash password
    const hashPassword = await bcrypt.hash(password, 10);

    const createdUser = await this.usersService.create({
      email,
      password: hashPassword,
    });

    return this.generateAccessToken(createdUser);
  }

  async signIn(email: string, pass: string): Promise<{ access_token: string }> {
    const user = await this.usersService.findOne(email);
    if (!user) {
      throw new NotFoundException('User not found');
    }
    const isPasswordValid = await bcrypt.compare(pass, user.password);
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid credentials');
    }
    return this.generateAccessToken(user);
  }

  private async generateAccessToken(user: User) {
    const payload = { sub: user.id, username: user.email };
    return {
      access_token: await this.jwtService.signAsync(payload),
    };
  }
}