Nest.js 系列——在 Nest.js 中使用JWT

前言

在接口调用中,我们经常会遇到需要用户登录的情况,这时候一般比较常用的就是 jwt 了。在 nest 中,可以使用 nest 提供的 jwt 模块来实现 jwt 的功能。来看看如何使用吧。

jwt

首先在 nest 中使用 jwt,需要先安装@nestjs/jwt 这个包

bash 复制代码
pnpm add @nestjs/jwt

安装完成后,我们需要在 app.module.ts 中引入 JwtModule 模块

typescript 复制代码
import { Module } from '@nestjs/common'
import { JwtModule } from '@nestjs/jwt'
import { AppController } from './app.controller'
import { AppService } from './app.service'

@Module({
  imports: [
    JwtModule.register({
      secret: 'water',
      signOptions: {
        expiresIn: '7d'
      }
    })
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

在这里我们使用了 register 方法来注册 jwt 模块,register 方法接收一个对象,对象中有两个属性,一个是 secret,一个是 signOptions。secret 是用来加密的,signOptions 是用来设置 token 的过期时间的。在这里我们设置了 7 天过期。当然还可以使用另外一种方式注册 jwt 模块,如下:

typescript 复制代码
import { Module } from '@nestjs/common'
import { JwtModule } from '@nestjs/jwt'
import { AppController } from './app.controller'
import { AppService } from './app.service'

@Module({
  imports: [
    JwtModule.registerAsync({
      async useFactory() {
        return {
          secret: 'water',
          signOptions: {
            expiresIn: '7d'
          }
        }
      }
    })
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

配置的参数和上面的一样,只是使用了 registerAsync 方法来注册 jwt 模块。registerAsync 方法接收一个对象,对象中有一个属性 useFactory,useFactory 是一个函数,函数中返回一个对象,对象中有两个属性,一个是 secret,一个是 signOptions。secret 是用来加密的,signOptions 是用来设置 token 的过期时间的。在这里我们设置了 7 天过期。

这样 jwt 模块就注册好了,接下来看如何使用

使用生成 token

在控制器中使用 jwt 模块,需要先引入 JwtService

typescript 复制代码
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'
import { AppService } from './app.service'
import { JwtService } from '@nestjs/jwt'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService, private readonly jwtService: JwtService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }
}

然后在控制器中使用 jwtService 的 sign 方法来生成 token

typescript 复制代码
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'
import { AppService } from './app.service'
import { JwtService } from '@nestjs/jwt'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService, private readonly jwtService: JwtService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }

  @Post('login')
  login(@Body() body) {
    const token = this.jwtService.sign({
      username: body.username,
      password: body.password
    })
    return {
      token
    }
  }
}

启动下生成 token 的测试项目,然后调用下 login 接口,可以看到返回了一个 token,如下:

使用验证 token

在接口调用的过程中,带上上面生成的 token,然后在控制器中使用 jwtService 的 verify 方法来验证 token

typescript 复制代码
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'
import { AppService } from './app.service'
import { JwtService } from '@nestjs/jwt'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService, private readonly jwtService: JwtService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }

  @Post('login')
  login(@Body() body) {
    const token = this.jwtService.sign({
      username: body.username,
      password: body.password
    })
    return {
      token
    }
  }

  @Get('profile')
  @UseGuards(AuthGuard('jwt'))
  getProfile(@Request() req) {
    return req.user
  }
}

在这里我们使用了 UseGuards 装饰器来使用 jwt 的验证,AuthGuard 的参数是 jwt,表示使用 jwt 的验证。然后在 getProfile 方法中使用 @Request() req 来获取用户信息。启动下验证 token 的测试项目,然后调用下 profile 接口。其实 AuthGuard('jwt')这个装饰器就是使用了 nest 提供的 AuthGuard 类,然后传入了 jwt,来看下 AuthGuard 类的源码

typescript 复制代码
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { JwtService } from '@nestjs/jwt'
import { Observable } from 'rxjs'
import { IS_PUBLIC_KEY } from '../decorators/public.decorator'

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector, private readonly jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    const isPublic = this.reflector.get(IS_PUBLIC_KEY, context.getHandler())
    if (isPublic) {
      return true
    }
    const request = context.switchToHttp().getRequest()
    const authHeader = request.headers.authorization
    if (!authHeader) {
      throw new UnauthorizedException('请登录')
    }
    const token = authHeader.split(' ')[1]
    try {
      const user = this.jwtService.verify(token)
      request.user = user
      return true
    } catch (error) {
      throw new UnauthorizedException('请登录')
    }
  }
}

通过封装的 AuthGuard 类,我们可以很方便的使用 jwt 的验证了。内部逻辑还是使用了 jwtService 的 verify 方法来验证 token 的。

使用 typeorm 结合 mysql 使用 jwt 实践登录注册功能

新建一个数据库 login_jwt,然后在新建一个 demo 项目。

bash 复制代码
nest new login-jwt -p pnpm

然后在项目中安装 typeorm 和 mysql2

bash 复制代码
pnpm add @nestjs/typeorm typeorm mysql2

然后在项目中配置好 typeorm

typescript 复制代码
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { AppController } from './app.controller'
import { AppService } from './app.service'

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: '123456',
      database: 'login_jwt',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
      logging: true,
      poolSize: 10,
      connectorPackage: 'mysql2'
    })
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

然后在项目中创建一个 user 模块

bash 复制代码
nest g resource user

修改 user.entity.ts 实体的字段

typescript 复制代码
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column({
    length: 50,
    comment: '用户名'
  })
  username: string

  @Column({
    length: 50,
    comment: '密码'
  })
  password: string

  @CreateDateColumn({
    comment: '创建时间'
  })
  create_time: Date

  @UpdateDateColumn({
    comment: '更新时间'
  })
  update_time: Date
}

重新启动下项目就能看到 user 表了。

在 user 模块中使用 typeorm 的 Repository 来操作数据库,先在 user.module.ts 中引入 User 实体并注册到 typeorm 中

typescript 复制代码
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { UserController } from './user.controller'
import { UserService } from './user.service'
import { User } from './user.entity'

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

然后在 user.service.ts 中使用 Repository 来操作数据库

typescript 复制代码
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './user.entity'
import { LoginDto } from './dto/login.dto'
import { RegisterDto } from './dto/register.dto'

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

  async register(register) {
    return await this.userRepository.findOne(register)
  }

  async login(login) {
    return await this.userRepository.save(login)
  }
}

然后在 user.controller.ts 中使用 UserService 来操作数据库

typescript 复制代码
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'
import { UserService } from './user.service'
import { JwtService } from '@nestjs/jwt'

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService, private readonly jwtService: JwtService) {}

  @Post('register')
  async register(@Body() registerDto: RegisterDto) {
    const user = await this.userService.register(registerDto)
  }

  @Post('login')
  async login(@Body() loginDto: LoginDto) {
    const user = await this.userService.login(loginDto)
  }
}

在 dto 文件夹中添加 login 和 register 的 dto 文件

typescript 复制代码
export class LoginDto {
  readonly username: string
  readonly password: string
}
ts 复制代码
export class RegisterDto {
  readonly username: string
  readonly password: string
}

上面的 user.service.ts 中只是定义了方法并没有具体实现,那么就先实现下注册逻辑,实现注册功能,然后在实现登录功能。

typescript 复制代码
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './user.entity'
import { LoginDto } from './dto/login.dto'
import { RegisterDto } from './dto/register.dto'
import * as crypto from 'crypto'

// 先定义一个md5加密的方法
function md5(s: string) {
  return crypto.createHash('md5').update(s).digest('hex')
}

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

  async register(register: RegisterDto): Promise<User> {
    const findUser = await this.userRepository.findOneBy({ username: register.username })

    if (findUser) {
      throw new Error('用户已存在')
    }

    const newUser = new User()
    newUser.username = register.username
    newUser.password = md5(register.password)

    try {
      await this.userRepository.save(newUser)
      return '注册成功'
    } catch (error) {
      throw new Error('注册失败')
    }

    // const user = await this.userRepository.create(register)
    // return await this.userRepository.save(user)
  }
}

使用 postman 调试下注册接口看下返回结果

看下数据库中的 user 表数据和 sql

如果在注册同一个用户就会提示用户已存在

然后实现下登录逻辑,登录逻辑就是先根据用户名查找用户,然后判断密码是否正确,如果正确就返回 token,如果不正确就提示密码错误。完善下上面的 login 控制器

typescript 复制代码
@Post('login')
  async login(@Body() loginDto: LoginDto) {
    const user = await this.userService.login(loginDto)
  }

然后在 user.service.ts 中实现登录逻辑

typescript 复制代码
async login(loginDto: LoginDto): Promise<User> {
    const findUser = await this.userRepository.findOneBy({ username: loginDto.username })
    if (!findUser) {
      throw new HttpException('用户不存在',200)
    }
    if (findUser.password !== md5(loginDto.password)) {
      throw new HttpException('密码错误',200)
    }
    return findUser
  }

使用 postman 调试下登录接口看下返回结果

可以看到登录的时候从数据库中查到这个用户的信息。并直接返回了这条数据,但是正常不会直接返回数据的,而是 token,所以需要在登录的时候返回 token。在登录的时候返回 token,需要在 user.module.ts 中引入 JwtModule 模块

安装 @nestjs/jwt

bash 复制代码
pnpm add @nestjs/jwt

然后在 app.module.ts 中引入 JwtModule 模块

typescript 复制代码
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { JwtModule } from '@nestjs/jwt'
import { AppController } from './app.controller'
import { AppService } from './app.service'

@Module({
  imorts:[
      TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
        password: '123456',
        database: 'login_jwt',
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: true,
        logging: true,
        poolSize: 10,
        connectorPackage: 'mysql2',
      }),
      JwtModule.register({
        global:true,
        secret: 'water',
        signOptions: {
          expiresIn: '7d'
        }
      })
  ]
})

在 user.controller.ts 引入 JwtService 并在登录成功的时候生成 token

typescript 复制代码
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'
import { UserService } from './user.service'
import { JwtService } from '@nestjs/jwt'

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService, private readonly jwtService: JwtService) {}

  @Post('login')
  async login(@Body() loginDto: LoginDto) {
    const findUser = await this.userService.login(loginDto)

    if (findUser) {
      const token = this.jwtService.sign({
        username: findUser.username,
        password: findUser.password
      })
      return {
        token
      }
    } else {
      throw new HttpException('密码错误', 200)
    }
  }
}

使用 postman 重新调试下登录接口看下返回结果

可以看到返回了 token,然后在登录的时候返回 token。如果想在接口上加接口的 token 鉴权,那就和上面封装一个守卫类似,具体逻辑就是取出接口请求中的 token 进行验证。

小结

从使用 jwt 生成 token 到配合 mysql 进行登录注册的练习,学习了如何在 nest 中使用 token 的方式,希望对你有帮助。

相关推荐
天草二十六_简村人2 小时前
spring-data-elasticsearch 3.2.4 实现桶bucket排序去重,实现指定字段的聚合搜索
java·spring boot·后端·spring·elasticsearch·架构·jenkins
拔剑纵狂歌2 小时前
ZooKeeper单机、集群模式搭建教程
分布式·后端·学习·zookeeper·中间件·架构·服务发现
jokerest1233 小时前
web——upload-labs——第五关——大小写绕过绕过
前端·后端
java_python源码3 小时前
[含文档+PPT+源码等]精品基于springboot实现的原生Andriod手机使用管理软件
java·spring boot·后端
技术猿188702783514 小时前
Spring Boot应用中的文件压缩与解压技术实践
java·spring boot·后端
码哥字节4 小时前
30+ 程序员降薪跳槽到银行,技术优势成了空谈。银行的职场环境,远比想象中的要复杂
后端
hlsd#4 小时前
go 集成swagger 在线接口文档
开发语言·后端·golang
大脑经常闹风暴@小猿5 小时前
Django 启用国际化支持—实现配置多国语言
后端·python·django
熊的猫5 小时前
实现 Toy-React , 实现 JSX 渲染
前端·javascript·chrome·react.js·webpack·前端框架·node.js
计算机-秋大田5 小时前
基于微信小程序的在线疫苗预约的设计与实现,LW+源码+讲解
spring boot·后端·微信小程序·小程序·vue