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 = null
    if (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> {
    // 生成 token
    const 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.ts

providers: [
    {
      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 true
    return super.canActivate(ctx)
  }
}

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

canActivate(ctx: ExecutionContext) {
    // 函数,类 是否允许 无 token 访问
    const allowAnon = this.reflector.getAllAndOverride<boolean>(ALLOW_ANON, [
      ctx.getHandler(), 
      ctx.getClass()
    ])
    if (allowAnon) return true
    const 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清除掉,再调用则提示我们未登录:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

kotlin与java的相互转换

Kotlin转java 将kotlin代码反编译成java Tools -> Kotlin -> Show Kotlin Bytecode 然后点击 【Decompile】 生成java代码 java转kotlin Code -> Convert Java File To Kotlin File

Netty入门指南:从零开始的异步网络通信

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Netty入门指南&#xff1a;从零开始的异步网络通信 前言Netty简介由来&#xff1a;发展历程&#xff1a;异步、事件驱动的编程模型&#xff1a; 核心组件解析通信协议高性能特性异步编程范式性能优化与…

2055041-59-1,NH-(PEG4-acid)2,能将基因和蛋白质导入到细胞内

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;2055041-59-1&#xff0c;NH-bis(PEG4-acid) HCl salt&#xff0c;NH-(PEG4-acid)2&#xff0c;NH-bis(PEG4-acid)&#xff0c;NH-BIS(四聚乙二醇-羧酸) 盐酸盐 一、基本信息 【产品简介】&#xff1a;NH bis (PEG4…

全新抖音视频下载软件|批量视频下载工具

随着抖音平台上精彩视频的不断涌现&#xff0c;许多用户希望能够方便地保存自己喜欢的视频内容&#xff0c;以便随时观看或分享给朋友。为了满足这一需求&#xff0c;我们基于C#开发了一款全新的视频下载软件&#xff0c;为您提供便捷、高效的视频获取体验。 主要功能模块&…

redis-Redis主从,哨兵和集群模式

一&#xff0c;Redis的主从复制 ​ 主机数据更新后根据配置和策略&#xff0c; 自动同步到备机的master/slaver机制&#xff0c;Master以写为主&#xff0c;Slave以读为主。这样做的好处是读写分离&#xff0c;性能扩展&#xff0c;容灾快速恢复。 1.1 环境搭建 如果你的redi…

Unity(第六部)向量的理解和算法

标量:只有大小的量。185 888 999 &#xff08;类似坐标&#xff09; 向量:既有大小&#xff0c;也有方向。&#xff08;类似以个体为主体的方向&#xff0c;前方一百米&#xff09; 向量的模:向量的大小。&#xff08;类似以个体为主体的方向&#xff0c;前方一百米、只取一百米…

计算机设计大赛 深度学习实现语义分割算法系统 - 机器视觉

文章目录 1 前言2 概念介绍2.1 什么是图像语义分割 3 条件随机场的深度学习模型3\. 1 多尺度特征融合 4 语义分割开发过程4.1 建立4.2 下载CamVid数据集4.3 加载CamVid图像4.4 加载CamVid像素标签图像 5 PyTorch 实现语义分割5.1 数据集准备5.2 训练基准模型5.3 损失函数5.4 归…

软件测试笔记(三):黑盒测试

1 黑盒测试概述 黑盒测试也叫功能测试&#xff0c;通过测试来检测每个功能是否都能正常使用。在测试中&#xff0c;把程序看作是一个不能打开的黑盒子&#xff0c;在完全不考虑程序内部结构和内部特性的情况下&#xff0c;对程序接口进行测试&#xff0c;只检查程序功能是否按…

nginx实现http反向代理及负载均衡

目录 一、代理概述 1、代理概念 1.1 正向代理&#xff08;Forward Proxy&#xff09; 1.2 反向代理&#xff08;Reverse Proxy&#xff09; 1.3 正向代理与反向代理的区别 2、同构代理与异构代理 2.1 同构代理 2.2 异构代理 2.3 同构代理与异构代理的区别 二、四层代…

【Web安全靶场】sqli-labs-master 21-37 Advanced-Injection

sqli-labs-master 21-37 Advanced-Injection 第一关到第二十关请见专栏 文章目录 sqli-labs-master 21-37 Advanced-Injection第二十一关-Cookie注入第二十二关-Cookie注入第二十三关-注释符过滤的报错注入第二十四关-二次注入第二十五关-过滤OR、AND双写绕过第二十五a关-过滤…

【SpringCloudAlibaba系列--OpenFeign组件】OpenFeign的配置、使用与测试以及OpenFeign的负载均衡

步骤一 准备两个服务&#xff0c;provider和consumer 本文使用kotlin语言 provider是服务的提供者&#xff0c;由provider连接数据库 RestController RequiredArgsConstructor RequestMapping("/provider/depart") class DepartController(private val departServ…

常用对象的遍历方法

var obj [{name: 1111,account: {01: { name: 1.1 },02: { name: 1.2 },03: { name: 1.3 },04: { name: 1.4 },05: { name: 1.5 },}} ]var nowObj obj[0].account;1、for…in 任意顺序遍历对象所有的可枚举属性&#xff08;包括对象自身的和继承的可枚举属性&#xff0c;不含…

flutter 封装webview和使用本地网页

最先看到flutter_webview_plugin 用法特别简单 flutter_webview_plugin | Flutter PackagePlugin that allow Flutter to communicate with a native Webview.https://pub-web.flutter-io.cn/packages/flutter_webview_plugin缺点&#xff1a; 没有实现js sdk的功能 没有办法 …

力扣简单递归:左叶子之和

思路&#xff1a;重点在于每层都记录val的值以减少递归调用次数 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int sumOfLeftLeaves(struct TreeNode* root){ if(rootNULL) {re…

Mycat核心教程--ZooKeeper集群搭建【三】

Mycat核心教程--ZooKeeper集群搭建 八、 ZooKeeper集群搭建8.1.ZooKeeper简介8.2.数据复制的好处8.3.Zookeeper设计目的8.4.zookeeper集群包括3种角色8.4.1.Leader角色8.4.2.Follower 角色8.4.3.Observer 角色 8.5.zookeeper集群工作流程8.6.zookeeper集群节点数量为奇数&#…

高性能Server的基石:reactor反应堆模式

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

图形判断类型

笔画数 笔画数这一考点。在国考、省考以及事业单位、三支一扶等各种公务员考试当中&#xff0c;都作为一个重要考点的存在。但很多同学仍然对于这部分知识点不清晰&#xff0c;比如不知道如何数奇点&#xff0c;数不清奇点&#xff0c;或无法快速识别这类题型&#xff0c;以致…

抖音橱窗怎么关闭?

路径&#xff1a;【抖音APP-我-电商带货-全部工具-账号管理-权限与账户-关闭电商权限】关闭橱窗带货权限。 0保、原0粉达人关闭电商权限后&#xff0c;后续均需要满足页面提示要求才可以进行电商权限的开通&#xff0c;建议慎重操作。

从新手到专家:AutoCAD 完全指南

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 AutoCAD是一款广泛用于工程设计和绘图的…

终于支持中文,开源手绘风格画板工具 Revezone 推荐!

Excalidraw 是一款老牌的手绘风格画板工具&#xff0c;颜值高&#xff0c;操作简单&#xff0c;GitHub 上拥有 69k 的 Star &#x1f449; https://excalidraw.com/ 可惜的是&#xff0c;Excalidraw 只有网页版&#xff0c;也不支持中文字体&#xff1a; 最近发现了国内开发者…