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

local身份验证

首先我们需要安装所需的包。Passport 提供了一种称为 passport-local 的策略,它实现了用户名/密码身份验证机制,适合我们对这部分用例的需求。安装本地验证策略依赖:

npm install --save passport-local
npm install --save-dev @types/passport-local

实现本地验证策略:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {constructor(private authService: AuthService) {//指定username为account字段super({ usernameField: 'account' });}async validate(username: string, password: string): Promise<any> {const user = await this.authService.validateUser(username, password);if (!user) {throw new UnauthorizedException();}return user;}
}

AuthService中实现验证账户和密码的功能:

async validateUser(account: string, password: string): Promise<UserEntity> {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, '您已被禁用,如需正常使用请联系管理员')return user}

创建本地认证守卫local-auth.guard.ts,并注册到auth.module.ts:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

我们把登录接口的实现放到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'
import { LocalAuthGuard } from 'src/common/guards/local-auth.guard'@ApiTags('登录')
@Controller()
export class AuthController {constructor(private readonly authService: AuthService) {}@Post('login')@ApiOperation({ summary: '登录' })@ApiResult(CreateTokenDto)@UseGuards(LocalAuthGuard)async login(@Req() req:any, @Body() loginUser:LoginUser ): Promise<CreateTokenDto> {return await this.authService.login(req.user)}
}

我们把登录逻辑放到AuthService,仅生成并返回access token等数据:

  async login(user: any): Promise<CreateTokenDto> {// 生成 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 }}

jwt身份验证

要实现我们的jwt身份验证策略jwt.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 JwtStrategy 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.findUser(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, LocalStrategy, JwtStrategy],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清除掉,再调用则提示我们未登录:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/264921.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕

一、电路接法 电路接法参照江科大视频。 二、相关代码及文件 说明&#xff1a;代码采用hal库&#xff0c;通过修改江科大代码实现。仅OLED.c文件关于引脚定义作了hal库修改&#xff0c;并将宏定义OLED_W_SCL(x)、OLED_W_SDA(x)作了相关修改。 1、OLED.c void OLED_I2C_Init(voi…

自动化行业文件数据\资料防泄密软件——天锐绿盾|@德人合科技

天锐绿盾是一款自动化行业文件数据防泄密软件&#xff0c;由德人合科技提供。该软件采用动态加解密技术&#xff0c;能够有效防止公司内部数据泄密&#xff0c;同时支持各种文件格式加密&#xff0c;如CAD、OFFICE、PDF、图纸等。 PC端&#xff1a;https://isite.baidu.com/sit…

项目管理工具git

git 1. git介绍1.1. 版本控制系统 2. 创建本地版本库2.1 概念2.2 操作步骤 3. 修改文件4. 练习: 添加一个本地项目到仓库5. 添加远程仓库5.1 添加远程仓库5.2 本地仓库同步到远程仓库5.3 克隆远程仓库到本地5.4 SSH设置 6. 分支管理6.1 创建分支6.2 切换分支6.3 合并分支6.4 解…

5.2 Ajax 数据爬取实战

目录 1. 实战内容 2、Ajax 分析 3、爬取内容 4、存入MySQL 数据库 4.1 创建相关表 4.2 数据插入表中 5、总代码与结果 1. 实战内容 爬取Scrape | Movie的所有电影详情页的电影名、类别、时长、上映地及时间、简介、评分&#xff0c;并将这些内容存入MySQL数据库中。 2、…

8.qt5使用opencv的库函数打开图片

1.配置opencv动态库的环境变量 2.在创建的qt工程中加入如下opencv代码&#xff0c;具体代码如下&#xff1a; 使用opencv库函数显示图片

c语言经典测试题4

1.题1 #include <stdio.h>//没有break的话&#xff0c;输入什么都会往下一直执行下去&#xff0c;而且default在最后就会全都执行 int main() {char c;int v0 0, v1 0, v2 0;do{switch (c getchar())// 输入ADescriptor{casea:caseA:casee:caseE:casei:caseI:caseo:…

数据结构2月21日

双向链表: func函数&#xff1a; #include <stdio.h> #include <stdlib.h> …

Springboot中如何记录好日志

Springboot中如何记录日志 日志体系整体介绍 日志一直在系统中占据这十分重要的地位&#xff0c;他是我们在系统发生故障时用来排查问题的利器&#xff0c;也是我们做操作审计的重要依据。那么如何记录好日志呢&#xff1f;选择什么框架来记录日志&#xff0c;是不是日志打越…

LeetCode142. 环形链表 II刷题详解

今天力扣刷到了一个特别有意思的题目&#xff0c;于是就写了下面的题解来加深以下理解。 142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 这个可以分为两大步去写&#xff0c;首先要判断链表是否有环&#xff0c;然后如果有环就去找到环的入口&#xff0c;没有环返…

Vue源码系列讲解——生命周期篇【七】(模板编译阶段)

目录 1. 前言 2. 模板编译阶段分析 2.1 两种$mount方法对比 2.2 完整版的vm.$mount方法分析 3. 总结 1. 前言 前几篇文章中我们介绍了生命周期的初始化阶段&#xff0c;我们知道&#xff0c;在初始化阶段各项工作做完之后调用了vm.$mount方法&#xff0c;该方法的调用标志…

Vue + Echarts页面内存占用高问题解决

Vue Echarts页面内存占用高问题解决 1.问题描述 目前使用的是Vue2 Echarts4.x的组合&#xff0c;页面如下所示。 就是一个类似于神策的数据看板页面&#xff0c;左侧是一个导航栏&#xff0c;右侧看板页面中包含很多个报表图片&#xff0c;其中报表页面中对Echarts图表进…

Excel的中高级用法

单元格格式&#xff0c;根据数值的正负分配不同的颜色和↑ ↓ 根据数值正负分配颜色 2-7 [蓝色]#,##0;[红色]-#,##0 分配颜色的基础上&#xff0c;根据正负加↑和↓ 2↑-7↓ 其实就是在上面颜色的代码基础上加个 向上的符号↑&#xff0c;或向下的符号↓ [蓝色]#,##0↑;[红色…

Python算法题集_图论(课程表)

Python算法题集_课程表 题207&#xff1a;课程表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【循环递归全算】2) 改进版一【循环递归缓存】3) 改进版二【循环递归缓存反向计算】4) 改进版三【迭代剥离计数器检测】 4. 最优算法5. 相关资源 本…

蓝桥杯算法赛 第 6 场 小白入门赛 解题报告 | 珂学家 | 简单场 + 元宵节日快乐

前言 整体评价 因为适逢元宵节&#xff0c;所以这场以娱乐为主。 A. 元宵节快乐 题型: 签到 节日快乐&#xff0c;出题人也说出来自己的心愿, 祝大家AK快乐! import java.util.Scanner;public class Main {public static void main(String[] args) {System.out.println(&qu…

现代化数据架构升级:毫末智行自动驾驶如何应对年增20PB的数据规模挑战?

毫末智行是一家致力于自动驾驶的人工智能技术公司&#xff0c;其前身是长城汽车智能驾驶前瞻分部&#xff0c;以零事故、零拥堵、自由出行和高效物流为目标&#xff0c;助力合作伙伴重塑和全面升级整个社会的出行及物流方式。 在自动驾驶领域中&#xff0c;是什么原因让毫末智行…

Unity3D 使用 Proto

一. 下载与安装 这里下载Google Protobuff下载 1. 源码用来编译CSharp 相关配置 2. win64 用于编译 proto 文件 二. 编译 1. 使用VS 打开 2. 点击最上面菜单栏 工具>NuGet 包管理器>管理解决方案的NuGet 管理包 版本一定要选择咱们一开始下载的对应版本否则不兼容&am…

linux中的权限

Linux 的权限 在了解Linux的权限之前&#xff0c;我们需要知道Linux的构成&#xff0c;Linux分为三个部分&#xff0c;内核、外部程序、以及用户。 内核&#xff1a; 内核一般是指Linux的操作系统&#xff0c;用来执行用户发送的指令 或者 拒绝执行用户发布指令时而发出的报…

Elasticsearch 创建index库 timeout

问题概述 使用 python 客户端 代码进行创建&#xff0c;【之前成功创建&#xff0c;但是现在出现报错&#xff0c;报错代码es_connection.client.indices.create】 def create_vector_index(dataset_index_name,vector_query_field,query_field):es_connection get_collentio…

高并发Server的基石:reactor反应堆模式

业务开发同学只关心业务处理流程。但是我们开发的程序都是运行服务端server上&#xff0c;服务端server接收到IO请求后&#xff0c;是如何处理请求并最终进入业务流程的呢&#xff1f;这里不得不提到reactor反应堆模型。reactor反应堆模型来源于大师Doug Lea在 《Sacalable io …

python Matplotlib Tkinter-->导出pdf报表

环境 python:python-3.12.0-amd64 包: matplotlib 3.8.2 reportlab 4.0.9 import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk import tkinter as tk import tkinter.messagebox as messagebox impor…