NestJS 实现 JWT

2023-03-08 下午后端 62 次浏览暂无评论

前言

在使用NestJS的过程中,用到了JWT做登录鉴权,在这里记录一下这部分内容。

首先附上官网介绍:https://docs.nestjs.com/security/authentication

具体实现

先安装相关依赖

npm install --save @nestjs/passport passport passport-local passport-jwt
npm install --save-dev @types/passport-local

添加 auth 相关文件,这里面处理 token 的颁发和解析

nest g module auth
nest g service auth

创建 auth/jwt.strategy.ts 文件,这是 jwt 的策略文件。

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.SECRET,
    });
  }

  async validate(payload: any) {
    return payload
  }
}

编辑 auth/auth.module.ts 文件

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt/dist';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    JwtModule.register({
      secret: process.env.SECRET, // 这里是jwt的密钥
      signOptions: { expiresIn: '60s' }
    })
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule { }

对应一些常量,我存储在了根目录下的 .env文件中,推荐包括数据库在内等关键信息都存储在这里面。

使用 process 对象需要在 app.module.ts 中添加以下配置

import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
    ...
  ],
  controllers: [AppController],
  providers: [AppService],
})

编辑 auth/auth.service.ts文件,这里我们只需要声明一个获取 token 的函数。

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

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

  public async getToken(payload) {
    return this.jwtService.sign(payload, { secret: process.env.SECRET })
  }
}

编辑user/user.module.ts文件,引入 AuthModule 模块

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { AuthModule } from 'src/auth/auth.module';

@Module({
  imports: [TypeOrmModule.forFeature([User]), AuthModule],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService]
})
export class UserModule { }

编辑 user/user.service.ts文件,当登录校验完成后,获取token返回给前端。

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { LoginUserDto } from './dto/login-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
import { Result, stateCode } from '../config/ResultType';
import { comparePassword } from 'src/utils/bcrypt';
import { AuthService } from 'src/auth/auth.service';

@Injectable()
export class UserService {
  constructor(@InjectRepository(User) private userRepository: Repository<User>, private authService: AuthService) { }

  // 用户登录
  async login(loginUserDto: LoginUserDto) {
    const { user_account } = loginUserDto
    const user = await this.userRepository.findOne({
      select: ['user_password'],
      where: {
        user_account
      }
    })
    if (user && comparePassword(loginUserDto.user_password, user.user_password)) {
      // 重新获取用户信息
      const res = await this.userRepository.findOne({ where: { user_account } })
      const token = await this.authService.getToken({ ...res })
      return Result.success({ ...res, token });
    } else {
      return Result.fail(stateCode.UNEXPECT, '用户名或密码错误')
    }
  }
  // ...
  async findOneByAccount(user_account: LoginUserDto['user_account']) {
    return await this.userRepository.findOne({
      select: ['user_id', 'user_password'],
      where: { user_account }
    });
  }

  async findOne(id: number) {
    return await this.userRepository.findOne({ where: { user_id: id } });
  }
}

编辑user/user.controller.ts文件,进行 jwt 的校验

import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Req } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { LoginUserDto } from './dto/login-user.dto';
import { AuthGuard } from '@nestjs/passport';
import { Result } from 'src/config/ResultType';

@Controller({ path: 'user', version: '1' })
export class UserController {
  constructor(private readonly userService: UserService) { }

  @Post('login')
  login(@Body() loginUserDto: LoginUserDto) {
    return this.userService.login(loginUserDto)
  }
    
  @UseGuards(AuthGuard('jwt'))
  @Get('whoami')
  async whoami(@Req() req) {
    const res = await this.userService.findOne(+req.user.user_id) // 通过jwt中的user_id,获取用户信息
    return Result.success(res)
  }
}

这样,通过访问 http://localhost:3000/v1/user/login,就可以获取到 token 了。

通过访问 http://localhost:3000/v1/user/whoami,在 header 里面携带 Authorization:Bearer token,就可以获取到登录用户的信息。

注意,这里的 Bearer 是必须要携带的,不然token会校验失败。

这里的 @UseGuards(AuthGuard('jwt'))是对单个接口进行校验,如果整个模块都需要 jwt 校验的话,可以将这行代码放至@Controller()上方,对整个模块的接口都进行校验。

总结

以上就是我对 NestJS 中使用 JWT 的一些个人总结。


目录

具体实现
总结
ICP备案号:鲁ICP备2020040322号