前言
在接口调用中,我们经常会遇到需要用户登录的情况,这时候一般比较常用的就是 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 的方式,希望对你有帮助。