1. 安装依赖并配置 JWT 首先,安装@nestjs/jwt包:
然后在.env文件中配置一下JWT的密钥与过期时间
1 2 3 # JWT配置 JWT_SECRET = bacdefg JWT_EXP = 2h
接着,在app.module.ts中导入JwtModule并做配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import { Module } from '@nestjs/common' import { AppController } from './app.controller' import { AppService } from './app.service' import { UserModule } from './user/user.module' import { TypeOrmModule } from '@nestjs/typeorm' import { ConfigModule } from '@nestjs/config' import { JwtModule } from '@nestjs/jwt' import { UserGuard } from './user/user.guard' @Module ({ imports : [TypeOrmModule .forRoot ({ type : 'mysql' , host : 'localhost' , port : 3306 , username : 'root' , password : '123456' , database : 'nest_demo' , entities : [__dirname + '/**/*.entity{.ts,.js}' ], synchronize : true , retryDelay : 500 , retryAttempts : 10 , autoLoadEntities : true , }), ConfigModule .forRoot ({ envFilePath : `.env` , isGlobal : true , }), JwtModule .registerAsync ({ global :true , useFactory : async () => { return { secret : process.env .JWT_SECRET , signOptions : { expiresIn : process.env .JWT_EXP }, } } }), UserModule ], controllers : [AppController ], providers : [AppService ], }) export class AppModule { }
2. 实现登录功能 在user.service.ts中实现用户登录,校验用户信息并生成JWT token:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import { User } from './entities/user.entity' ;import { Injectable } from '@nestjs/common' ;import { CreateUserDto } from './dto/create-user.dto' ;import { UpdateUserDto } from './dto/update-user.dto' ;import { InjectRepository } from '@nestjs/typeorm' ;import { Repository } from 'typeorm' ;import { hash, verify } from '../utils/md5' import { JwtService } from '@nestjs/jwt' @Injectable ()export class UserService { constructor ( @InjectRepository (User) private usersRepository: Repository<User>, private jwtService: JwtService, ) { } async login (loginDto: CreateUserDto ) { const { username, password } = loginDto const user = await this .findByUsername (username) if (!user) return '用户不存在' if (!verify (password, user.password , process.env .MD5_SALT )) { return '密码错误' } const payload = { username : user.username , id : user.id }; return await this .jwtService .signAsync (payload); } async findByUsername (username: string ) { const user = await this .usersRepository .findOne ({ where : { username }, select : ['id' , 'username' , 'password' ] }) return user } }
登录成功后,返回的token如下:
1 2 3 4 5 6 { "code" : 200 , "success" : "请求成功" , "timestamp" : "2024/9/15 23:38:31" , "data" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
3. 导航守卫 Guard 实现 导航守卫用于拦截路由导航,检查用户是否已登录。
通过nest g gu user生成守卫user.guard.ts: 规定前端请求头字段为authorization,并且以Bearer ${token}(约定俗称)这种形式传值,将guard改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import { CanActivate , ExecutionContext , HttpException , HttpStatus , Injectable } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { Reflector } from '@nestjs/core' @Injectable ()export class UserGuard implements CanActivate { constructor ( private jwtService: JwtService, private reflector: Reflector, ) { } async canActivate (context : ExecutionContext ): Promise <boolean > { const request = context.switchToHttp ().getRequest (); const token = this .extractTokenFromHeader (request); if (!token) { throw new HttpException ('验证不通过' , HttpStatus .FORBIDDEN ); } try { const payload = await this .jwtService .verifyAsync (token, { secret : process.env .JWT_SECRET , }); request['user' ] = payload; } catch { throw new HttpException ('token验证失败' , HttpStatus .FORBIDDEN ); } return true ; } private extractTokenFromHeader (request : Request ): string | undefined { const [type , token] = (request.headers as { authorization?: string }).authorization ?.split (' ' ) ?? []; return type === 'Bearer' ? token : undefined ; } }
测试守卫 在user.module.ts中将UserGuard配置为全局守卫:
1 2 3 4 5 6 7 8 9 10 11 12 13 import { Controller , Post , Body , UseGuards } from '@nestjs/common' ;import { UserGuard } from './user.guard' ;@Controller ('user' )export class AuthController { constructor (private readonly authService: AuthService ) {} @UseGuards (UserGuard ) @Post ('test' ) test ( ) { return 1 ; } }
请求结果:
1 2 3 4 5 6 7 { "success" : false , "timestamp" : "2024/9/15 23:54:33" , "message" : "验证不通过" , "code" : 403 , "path" : "/api/v1/user/3" }
配置全局守卫 现在我们注册成全局守卫,在user.module.ts引入APP_GUARD并将UserGuard注入,这样它就成为了全局守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { APP_GUARD } from "@nestjs/core" ;import { UserGuard } from "./user.guard" ;@Module ({ controllers : [UserController ], providers : [ UserService , { provide : APP_GUARD , useClass : UserGuard , }, ], imports : [TypeOrmModule .forFeature ([User ]), CacheModule ], }) export class UserModule {}
自定义装饰器:免登录访问 这时候将user.controller.ts中的装饰器@UseGuards去掉 发现守卫依然有效 在业务中并不是所有接口都需要验证,比如登录接口,我们可以通过自定义装饰器设置元数据来处理 执行nest g d public生成一个public.decorator.ts创建一个装饰器设置一下元数据isPublic为true
1 2 3 import { SetMetadata } from '@nestjs/common' export const Public = ( ) => SetMetadata ('isPublic' , true )
然后在user.guard.ts中通过Reflector取出当前的isPublic,如果为true(即用了@Public进行装饰过),则不再进行token判断直接放行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { CanActivate , ExecutionContext , HttpException , HttpStatus , Injectable } from '@nestjs/common' import { JwtService } from '@nestjs/jwt' import { Reflector } from '@nestjs/core' @Injectable ()export class UserGuard implements CanActivate { constructor ( private jwtService: JwtService, private reflector: Reflector, ) { } async canActivate (context : ExecutionContext ): Promise <boolean > { const isPublic = this .reflector .getAllAndOverride <boolean >('isPublic' , [ context.getHandler (), context.getClass (), ]) if (isPublic) { return true ; } }
在不需要验证的接口上添加@Public()装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { Controller , Get } from '@nestjs/common' ;import { AppService } from './app.service' ;import { Public } from './public/public.decorator' ;@Controller ()export class AppController { constructor (private readonly appService: AppService ) {} @Public () @Get () getHello (): string { return this .appService .getHello (); } }
返回结果如下
1 2 3 4 5 6 { "code" : 200 , "success" : "请求成功" , "timestamp" : "2024/9/16 00:04:17" , "data" : "Hello World!" }
4. 获取当前用户信息 执行nest g d user生成user.decorator.ts,实现一个用于获取当前用户信息的装饰器:
1 2 3 4 5 6 7 8 9 import { createParamDecorator, ExecutionContext , SetMetadata } from '@nestjs/common' ;export const User = createParamDecorator ((data: string , ctx: ExecutionContext ) => { return ctx.switchToHttp ().getRequest ().user [data]; })
在user.controller.ts中添加接口获取用户信息:
1 2 3 4 5 6 7 8 9 10 11 import { Controller , Get , UseGuards } from '@nestjs/common' import { User } from './user.decorator' ;@Controller ('user' )export class AuthController { constructor (private readonly authService: AuthService ) {} @Get ('test' ) test (@User ('username' ) username: string ) { return username; } }
总结 本文介绍了如何在 NestJS 中通过 JwtModule 配置 JWT 认证,使用全局守卫拦截路由,并自定义装饰器获取用户信息或处理免登录路由。通过这种方式,我们可以很方便地实现用户认证和路由保护,确保应用的安全性和灵活性。