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 的方式,希望对你有帮助。

相关推荐
wfsm几秒前
spring事件使用
java·后端·spring
微风粼粼18 分钟前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
rebel1 小时前
若依框架整合 CXF 实现 WebService 改造流程(后端)
java·后端
极客悟道1 小时前
颠覆传统虚拟化:在Docker容器中运行Windows系统的开源黑科技
前端·后端
调试人生的显微镜2 小时前
WebView 中 Cookie 丢失怎么办?跨域状态不同步的调试与修复经验
后端
一生躺平的仔2 小时前
NestJS Swagger 使用说明文档
nestjs
weixin_437398212 小时前
转Go学习笔记(2)进阶
服务器·笔记·后端·学习·架构·golang
极客悟道2 小时前
巧解 Docker 镜像拉取难题:无需梯子和服务器,拉取数量无限制
后端·github
aiopencode2 小时前
iOS 出海 App 安全加固指南:无源码环境下的 IPA 加固与防破解方法
后端
liangdabiao2 小时前
AI一人公司?先搞定聚合支付!一天搞定全能的聚合支付系统
后端