Nest.js权限管理系统开发(八)jwt登录

2024-02-27 03:44

本文主要是介绍Nest.js权限管理系统开发(八)jwt登录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

安装相关依赖

虽然仅使用@nestjs/jwt就能实现身份验证的功能,但是使用passport能在更高层次上提供更多便利。Passport 拥有丰富的 strategies 生态系统,实现了各种身份验证机制。虽然概念简单,但你可以选择的 Passport 策略集非常丰富且种类繁多。Passport 将这些不同的步骤抽象为一个标准模式,@nestjs/passport 模块将这个模式封装并标准化为熟悉的 Nest 结构。

安装相关依赖:

npm i passport passport-jwt @nestjs/passport @nestjs/jwt
npm i --save-dev @types/passport-jwt

创建身份验证模块

我们将从生成一个 AuthModule 开始,然后在其中生成一个 AuthService 和一个 AuthController。我们将使用 AuthService 来实现身份验证逻辑,使用 AuthController 来公开身份验证端点。

$ nest g module auth
$ nest g controller auth
$ nest g service auth

我们把登录接口的实现放到AuthController:

import { Body, Controller, Post, Req } from '@nestjs/common'
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'import { AllowAnon } from '../common/decorators/allow-anon.decorator'
import { ApiResult } from '../common/decorators/api-result.decorator'import { LoginUser } from './dto/login-user.dto'
import { CreateTokenDto } from './dto/create-token.dto'
import { AuthService } from './auth.service'@ApiTags('登录')
@Controller()
export class AuthController {constructor(private readonly authService: AuthService) {}@Post('login')@ApiOperation({ summary: '登录' })@ApiResult(CreateTokenDto)async login(@Body() dto: LoginUser): Promise<CreateTokenDto> {return await this.authService.login(dto.account, dto.password)}
}

我们把登录逻辑放到AuthService

 async login(account: string, password: string): Promise<CreateTokenDto> {let user = nullif (validPhone(account)) {// 手机登录user = await this.userService.findOneByPhone(account)} else if (validEmail(account)) {// 邮箱user = await this.userService.findOneByEmail(account)} else {// 帐号user = await this.userService.findOneByAccount(account)}if (!user) throw new ApiException(ApiErrorCode.USER_PASSWORD_INVALID, '帐号或密码错误')const checkPassword = await compare(password, user.password)if (!checkPassword) throw new ApiException(ApiErrorCode.USER_PASSWORD_INVALID, '帐号或密码错误')if (user.status === 0)throw new ApiException(ApiErrorCode.USER_ACCOUNT_FORBIDDEN, '您已被禁用,如需正常使用请联系管理员')// 生成 tokenconst data = this.genToken({ id: user.id })return data}/*** 生成 token 与 刷新 token* @param payload* @returns*/genToken(payload: { id: string }): CreateTokenDto {const accessToken = `Bearer ${this.jwtService.sign(payload)}`const refreshToken = this.jwtService.sign(payload, { expiresIn: this.config.get('jwt.refreshExpiresIn') })return { accessToken, refreshToken }}

要实现我们的身份验证策略auth.strategy.ts:

import { PassportStrategy } from '@nestjs/passport'
import { Strategy, ExtractJwt } from 'passport-jwt'
import { ConfigService } from '@nestjs/config'
import { UnauthorizedException, Injectable } from '@nestjs/common'import { AuthService } from './auth.service'@Injectable()
export class AuthStrategy extends PassportStrategy(Strategy) {/*** 这里的构造函数向父类传递了授权时必要的参数,在实例化时,父类会得知授权时,客户端的请求必须使用 Authorization 作为请求头,* 而这个请求头的内容前缀也必须为 Bearer,在解码授权令牌时,使用秘钥 secretOrKey: 'secretKey' 来将授权令牌解码为创建令牌时的 payload。*/constructor(private readonly authService: AuthService, private readonly config: ConfigService) {super({jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),secretOrKey: config.get('jwt.secretkey'),})}/*** validate 方法实现了父类的抽象方法,在解密授权令牌成功后,即本次请求的授权令牌是没有过期的,* 此时会将解密后的 payload 作为参数传递给 validate 方法,这个方法需要做具体的授权逻辑,比如这里我使用了通过用户名查找用户是否存在。* 当用户不存在时,说明令牌有误,可能是被伪造了,此时需抛出 UnauthorizedException 未授权异常。* 当用户存在时,会将 user 对象添加到 req 中,在之后的 req 对象中,可以使用 req.user 获取当前登录用户。*/async validate(payload: { id: string }) {const user = await this.authService.validateUser(payload)// 如果用用户信息,代表 token 没有过期,没有则 token 已失效if (!user) throw new UnauthorizedException()return user}
}

我们需要配置 AuthModule 以使用我们刚刚定义的 Passport 功能。将 auth.module.ts 更新为如下所示:

@Module({controllers:[AuthController],imports: [UserModule,JwtModule.registerAsync({imports: [ConfigModule],useFactory: async (config: ConfigService) => ({secret: config.get('jwt.secretkey'),signOptions: {expiresIn: config.get('jwt.expiresin'),},}),inject: [ConfigService],}),PassportModule.register({ defaultStrategy: 'jwt' }),],providers: [AuthService, AuthStrategy],exports: [PassportModule, AuthService],
})
export class AuthModule {}

将 AuthModule 绑定到 AppModule 上后,我们可以在 controller 上使用守卫装饰器 @UseGuards(AuthGuard)进行验证。但是每个controller或者路由上都要标注一下很不方便。这时候我们可以使用全局守卫:

//app.module.tsproviders: [{provide: APP_GUARD,useClass: JwtAuthGuard,},],

 现在要求接口请求头都要带上token,但是登录注册接口是不需要token的,因此我们需要一种机制来取消接口的校验。

创建一个装饰器allow-anon.decorator.ts:

import { SetMetadata } from '@nestjs/common'export const ALLOW_ANON = 'allowAnon'
/*** 允许 接口 不校验 token*/
export const AllowAnon = () => SetMetadata(ALLOW_ANON, true)

继承守卫,并重新实现canActivate,我们需要 JwtAuthGuard 在找到 "AllowAnon" 元数据时返回 true,否则使用内置的实现:

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {constructor(private readonly reflector: Reflector,//private readonly userService: UserService) {super()}canActivate(ctx: ExecutionContext) {// 函数,类 是否允许 无 token 访问const allowAnon = this.reflector.getAllAndOverride<boolean>(ALLOW_ANON, [ctx.getHandler(), ctx.getClass()])if (allowAnon) return truereturn super.canActivate(ctx)}
}

 当然,如果你想自定义异常提示,可以在前面进行判断,并抛出自定义的异常信息:

canActivate(ctx: ExecutionContext) {// 函数,类 是否允许 无 token 访问const allowAnon = this.reflector.getAllAndOverride<boolean>(ALLOW_ANON, [ctx.getHandler(), ctx.getClass()])if (allowAnon) return trueconst req = ctx.switchToHttp().getRequest()const accessToken = req.get('Authorization')if (!accessToken) throw new ForbiddenException('请先登录')const atUserId = this.authService.verifyToken(accessToken)if (!atUserId) throw new UnauthorizedException('当前登录已过期,请重新登录')return super.canActivate(ctx)}

swagger添加 jwt token

 因为有些接口需要登录才能访问,所以需要在 swagger 中配置 token才能成功调用。只需要在 main.ts 再加个 addBearerAuth()函数即可。

const swaggerOptions = new DocumentBuilder().setTitle('Nest-Admin App').setDescription('Nest-Admin App 接口文档').setVersion('2.0.0').addBearerAuth().build()

然后在需要认证的接口加上@ApiBearerAuth()装饰器即可,比如

@Post('/update/token')@ApiOperation({ summary: '刷新token' })@ApiResult(CreateTokenDto)@ApiBearerAuth()async updateToken(@Req() req:any): Promise<CreateTokenDto> {return await this.authService.updateToken(req.user.id)}

点击Authorization,将调用登录接口取得的 token 输入进去即可调用加了权限的接口了

调用它:

我们发现验证通过了。如果我们把token清除掉,再调用则提示我们未登录:

这篇关于Nest.js权限管理系统开发(八)jwt登录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/751116

相关文章

SpringBoot开发中十大常见陷阱深度解析与避坑指南

《SpringBoot开发中十大常见陷阱深度解析与避坑指南》在SpringBoot的开发过程中,即使是经验丰富的开发者也难免会遇到各种棘手的问题,本文将针对SpringBoot开发中十大常见的“坑... 目录引言一、配置总出错?是不是同时用了.properties和.yml?二、换个位置配置就失效?搞清楚加

CSS3打造的现代交互式登录界面详细实现过程

《CSS3打造的现代交互式登录界面详细实现过程》本文介绍CSS3和jQuery在登录界面设计中的应用,涵盖动画、选择器、自定义字体及盒模型技术,提升界面美观与交互性,同时优化性能和可访问性,感兴趣的朋... 目录1. css3用户登录界面设计概述1.1 用户界面设计的重要性1.2 CSS3的新特性与优势1.

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

基于Python开发Windows屏幕控制工具

《基于Python开发Windows屏幕控制工具》在数字化办公时代,屏幕管理已成为提升工作效率和保护眼睛健康的重要环节,本文将分享一个基于Python和PySide6开发的Windows屏幕控制工具,... 目录概述功能亮点界面展示实现步骤详解1. 环境准备2. 亮度控制模块3. 息屏功能实现4. 息屏时间

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

使用Python开发一个现代化屏幕取色器

《使用Python开发一个现代化屏幕取色器》在UI设计、网页开发等场景中,颜色拾取是高频需求,:本文主要介绍如何使用Python开发一个现代化屏幕取色器,有需要的小伙伴可以参考一下... 目录一、项目概述二、核心功能解析2.1 实时颜色追踪2.2 智能颜色显示三、效果展示四、实现步骤详解4.1 环境配置4.

Python使用smtplib库开发一个邮件自动发送工具

《Python使用smtplib库开发一个邮件自动发送工具》在现代软件开发中,自动化邮件发送是一个非常实用的功能,无论是系统通知、营销邮件、还是日常工作报告,Python的smtplib库都能帮助我们... 目录代码实现与知识点解析1. 导入必要的库2. 配置邮件服务器参数3. 创建邮件发送类4. 实现邮件

Java中的登录技术保姆级详细教程

《Java中的登录技术保姆级详细教程》:本文主要介绍Java中登录技术保姆级详细教程的相关资料,在Java中我们可以使用各种技术和框架来实现这些功能,文中通过代码介绍的非常详细,需要的朋友可以参考... 目录1.登录思路2.登录标记1.会话技术2.会话跟踪1.Cookie技术2.Session技术3.令牌技

如何搭建并配置HTTPD文件服务及访问权限控制

《如何搭建并配置HTTPD文件服务及访问权限控制》:本文主要介绍如何搭建并配置HTTPD文件服务及访问权限控制的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、安装HTTPD服务二、HTTPD服务目录结构三、配置修改四、服务启动五、基于用户访问权限控制六、

基于Python开发一个有趣的工作时长计算器

《基于Python开发一个有趣的工作时长计算器》随着远程办公和弹性工作制的兴起,个人及团队对于工作时长的准确统计需求日益增长,本文将使用Python和PyQt5打造一个工作时长计算器,感兴趣的小伙伴可... 目录概述功能介绍界面展示php软件使用步骤说明代码详解1.窗口初始化与布局2.工作时长计算核心逻辑3