【NestJS】JWT 鉴权

news/2024/4/26 2:56:27/文章来源:https://blog.csdn.net/Superman_H/article/details/129173137

Passport 是一个 NodeJS 鉴权库

JWT 认证的交互流程:浏览器发起请求,服务端对用户名和密码进行验证。如果身份验证通过,服务端会基于用户信息生成 token 字符串,并将其响应给浏览器。浏览器会将 token 字符串存储起来。往后的每次请求,浏览器都会以请求头的形式带上 token 字符串。服务端收到请求后,会解析 token 字符串。如果 token 验证通过,则正常返回数据,否则抛出错误。



搭建项目

  1. nest g module auth nest g service auth
  2. nest g module users nest g service users

UserModule

编写 UsersService:

import { Injectable } from '@nestjs/common';interface User {userId: number;username: string;password: string;
}@Injectable()
export class UsersService {private readonly users: User[];constructor() {// 这里把用户列表写死了. 在真正的应用程序中,用户列表应该从数据库获取this.users = [{userId: 1,username: 'john',password: 'riddle',},{userId: 2,username: 'chris',password: 'secret',},{userId: 3,username: 'maria',password: 'guess',},];}async findOne(username: string) {return this.users.find((user) => user.username === username);}
}

在 UsersModule 中,将 UsersService 导出,供其他模块使用:

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';@Module({providers: [UsersService],exports: [UsersService], // 导出 UsersService
})
export class UsersModule {}

AuthModule

在 AuthModule 中导入 UserModule:

import { Module } from '@nestjs/common';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';@Module({imports: [UsersModule], // 导入 UsersModuleproviders: [AuthService],
})
export class AuthModule {}

编写 AuthService:

import { Injectable } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';@Injectable()
export class AuthService {constructor(private readonly usersService: UsersService) {}/* 检查用户是否已存在 + 校验密码 */async validateUser(username: string, pwd: string) {const user = await this.usersService.findOne(username); // 获取用户if (user && user.password === pwd) {const { password, ...result } = user; // 剔除 passwordreturn result; // 返回用户信息}return null; // 用户不存在 / 密码错误}
}

在实际项目中,应该使用封装了加密单向哈希算法的库对密码进行加密,然后在数据库中存储加密后的密码。在检验密码时,对输入的密码也进行加密处理,然后再与数据库中存储的加密后的密码进行比较即可。



本地策略

  1. npm i @nestjs/passport passport passport-local
  2. npm i -D @types/passport-local

在 auth 目录下编写本地策略的配置文件 local.strategy.ts

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';@Injectable() // 通过 PassportStrategy 使用 local 策略
export class LocalStrategy extends PassportStrategy(Strategy) {constructor(private readonly authService: AuthService) {super();}async validate(username: string, password: string) {const user = await this.authService.validateUser(username, password);if (!user) {throw new UnauthorizedException(); // 返回 '未授权' 错误 (401)}return user; // 返回用户信息}
}
  • 对于每个策略,Passport 都会使用与策略对应的参数调用 verify 函数 (使用 @nestjs/Passport 中的 validate() 方法实现)。
  • 对于本地策略,Passport 需要一个具有以下签名的 validate() 方法:validate(username: string, password: string): any

如果找到了用户并且凭据有效,则返回该用户;如果没有找到用户或凭据已失效,则抛出异常。


配置 AuthModule 来使用刚才定义的 Passport 特性:

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';@Module({imports: [UsersModule, PassportModule], // 引入 PassportModuleproviders: [AuthService, LocalStrategy], // 注册 LocalStrategy
})
export class AuthModule {}



登录路由

现在就可以实现一个简单的 /login 路由,并应用内置的守卫来启动 Passport-local 流

import { Controller, Req, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport/dist/auth.guard';
import { Request } from 'express';@Controller()
export class AppController {@UseGuards(AuthGuard('local')) // 启用本地策略@Post('login')async getHello(@Req() request: Request) {// Passport 会根据 validate() 方法的返回值创建一个 user 对象// 并以 req.user 的形式分配给请求对象return request.user;}
}

img

img


除了 @UseGuards(AuthGuard('local')),也可以使用自己创建的守卫:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {} // 继承 AuthGuard('local')
import { Controller, Req, Post, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { LocalAuthGuard } from './auth/local-auth.guard';@Controller()
export class AppController {@UseGuards(LocalAuthGuard) // 使用自己创建的守卫@Post('login')async getHello(@Req() request: Request) {return request.user;}
}



JWT 策略

获取 token

  1. npm i @nestjs/jwt passport-jwt
  2. npm i @types/passport-jwt -D

@nest/jwt 是一个实用程序包,可以帮助 JWT 操作;passport-jwt 包是实现 JWT 策略的 Passport 包


在 AuthModule 中引入 JwtModule:

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';@Module({imports: [UsersModule, PassportModule, JwtModule], // 引入 JwtModuleproviders: [AuthService, LocalStrategy],
})
export class AuthModule {}

编写 AuthService,添加 login() 方法:

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt/dist';
import { UsersService } from 'src/users/users.service';@Injectable()
export class AuthService {constructor(private readonly usersService: UsersService,private readonly jwtService: JwtService,) {}async validateUser(username: string, pwd: string) {const user = await this.usersService.findOne(username);if (user && user.password === pwd) {const { password, ...result } = user;return result;}return null;}async login(user: any) {const payload = { username: user.username, sub: user.userId };return {// 使用 jwtService.sign() 基于 payload 生成 token 字符串access_token: this.jwtService.sign(payload), };}
}

配置 token 签名密文:

export const jwtConstants = {secret: 'secretKey', // token 签名密文 (密钥)
};

不要公开此密钥。在生产系统中,您必须使用适当的措施来保护这个密钥,比如机密库、环境变量或配置服务。


更新 auth.module.ts,对 JwtModule 进行配置,并导出 AuthService:

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { LocalStrategy } from './local.strategy';@Module({imports: [UsersModule,PassportModule,/* 配置 JwtModule */JwtModule.register({secret: jwtConstants.secret, // 使用 token 签名密文signOptions: { expiresIn: '60s' }, // 设置 token 的有效期}),],providers: [AuthService, LocalStrategy],exports: [AuthService], // 导出 AuthService
})
export class AuthModule {}

有关 Nest JwtModule 的更多信息请参见 GitHub - @nestjs/jwt

有关可用配置选项的更多信息请参见 GitHub - node-jsonwebtoken


更新 /login 路由,返回 JWT:

import { Controller, Req, Post, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard } from '@nestjs/passport/dist/auth.guard';
import { AuthService } from './auth/auth.service';@Controller()
export class AppController {constructor(private readonly authService: AuthService) {} // 依赖注入@UseGuards(AuthGuard('local'))@Post('login')async getHello(@Req() request: Request) {return this.authService.login(request.user); // 调用 login 方法}
}

img


检验 token

在 auth 目录下编写本地策略的配置文件 jwt.strategy.ts

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {constructor() {super({jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),ignoreExpiration: false,secretOrKey: jwtConstants.secret,});}async validate(payload: any) {return { userId: payload.sub, username: payload.username };}
}

JWT 策略需要一些初始化,我们可以在这里阅读关于可用选项的更多信息。在上例中,这些选项是:

  • jwtFromRequest:提供从请求中提取 JWT 的方法
  • ignoreExpiration:默认为 false,表示由 Passport 模块来检查 JWT 是否已过期。如果请求携带了一个过期的 JWT,该请求将会被拒绝,并响应 401
  • secretOrkey:用于生成密钥的签名。可用的其他选项:如 pemo 编码的公钥,可能更适合于生产应用程序 (有关更多信息,请参见此处)。如前所述,不要把这个秘密公开。

对于 JWT 策略,Passport 会先验证 token 字符串,并将其还原成用户信息。然后调用 validate() 方法,还原出来的用户信息会作为参数传入该方法。上例中,validate() 的逻辑很简单:只是返回一个包含 userIdusername 属性的对象。Passport 会基于 validate() 方法的返回值构建一个user 对象,并将其作为属性附加到请求对象上。


在 AuthModule 中添加 JwtStrategy 作为提供者:

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';@Module({imports: [UsersModule,PassportModule,JwtModule.register({secret: jwtConstants.secret,signOptions: { expiresIn: '60s' },}),],providers: [AuthService, LocalStrategy, JwtStrategy], // 注册 JwtStrategyexports: [AuthService],
})
export class AuthModule {}

通过导入 JWT 签名时使用的相同密钥,我们可以确保 Passport 执行的验证阶段和 AuthService 执行的签名阶段使用公共密钥。


更新 app.controller.ts 文件,使用 JWT 鉴权:

import { Controller, Req, Post, UseGuards, Get } from '@nestjs/common';
import { Request } from 'express';
import { AuthGuard } from '@nestjs/passport/dist/auth.guard';
import { AuthService } from './auth/auth.service';@Controller()
export class AppController {constructor(private readonly authService: AuthService) {}@UseGuards(AuthGuard('local'))@Post('login')async getHello(@Req() request: Request) {return this.authService.login(request.user);}@UseGuards(AuthGuard('jwt')) // 使用 JWT 鉴权@Get('profile')getProfile(@Req() request: Request) {return request.user; // 返回用户信息}
}

当我们请求 GET /profile 路由时,保护程序将自动调用我们的 passport-jwt 自定义配置逻辑,验证 JWT ,并将用户属性分配给请求对象。

img

img

img

注意,在 AuthModule 中,我们将 JWT 配置为 60 秒过期。如果您在验证之后等待 60 秒再尝试 GET /profile 请求,您将收到 401 未授权响应。这是因为 Passport 会自动检查 JWT 的过期时间,从而省去了在应用程序中这样做的麻烦。



默认策略

在 AppController 中,使用 @UseGuards(AuthGuard(XXX)) 装饰器需要传递策略的名称 XXX。我们可以声明一个默认策略,就不必再传入名称了。

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport/dist';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';@Module({imports: [UsersModule,PassportModule.register({ defaultStrategy: 'jwt' }), // 配置默认策略JwtModule.register({secret: jwtConstants.secret,signOptions: { expiresIn: '60s' },}),],providers: [AuthService, LocalStrategy, JwtStrategy],exports: [AuthService],
})
export class AuthModule {}



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

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

相关文章

vscode远程调试python

目的 注意:这里我们想要实现的是:用vscode 使用remote ssh打开project,然后直接在project里面进行debug,而不需要 在本地vscode目录打开一样的project。 假设大家已经会使用remote ssh打开远程服务器的代码了,那么只…

Photon Vectorized Engine 学习记录

Photon Hash Aggregation Vectorization Photon Hash Join 的向量化的要点是:使用开放地址法。步骤: 向量化计算 hash 值基于 hash 向量化计算 bucket 下标,得到 bucket index 向量基于 bucket index 向量中记录的下标找到 bucket&#xff…

(考研湖科大教书匠计算机网络)第六章应用层-第四节:域名系统DNS

获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:DNS概述二:层次域名结构(1)概述(2)顶级域名分类(3)因特网命名空…

部署跨云容灾的五大难点

为什么企业需要跨云容灾? 据统计,全球已有70%的企业使用云计算服务。上云帮助企业更高效地管理数据资产,但它并非绝对安全。如停电、漏水等机房事故;地震、火灾等自然性灾害;亦或是人为失误,都有可能造成数…

视频技术基础知识

一、视频图像基础 像素:图像的基本单元,即一个带有颜色的小块分辨率:图像的大小或尺寸,用像素个数来表示。原始图像分辨率越高,图像就越清晰位深:存储每位像素需要的二进制位数;位深越大&#…

华为OD机试 C++ 实现 - 第 N 个排列

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

模电学习7. 三极管特性曲线与静态工作点

模电学习7. 三极管特性曲线与静态工作点一、三极管的伏安特性曲线1. 三极管的伏安特性曲线2. 三极管的静态工作点二、合适的静态工作点选择1. 合适静态工作点条件2. 静态工作点的确定三、使用立创EDA仿真查看静态工作点1. 搭建如下图所示测试电路2. 点击菜单仿真、仿真设置3. 运…

springboot整合springdata jpa全能书

一:spring data jpa介绍 spring data:其实spring data就是spring提供了一个操作数据的框架。而spirng data jpa只是spring data框架下的一个基于jpa标准操作数据的模块。 spring data jpa:基于jpa的标准对数据进行操作。简化操作持久层的代码。只需要编…

【离线数仓-4-数据仓库设计】

离线数仓-4-数据仓库设计离线数仓-4-数据仓库设计1.数据仓库分层规划2.数据仓库构建流程1.数据调研1.业务调研2.需求分析3.总结2.明确数据域3.构建业务总线矩阵&维度模型设计4.明确统计指标1.指标体系相关概念1.原子指标2.派生指标3.衍生指标2.指标体系对于数仓建模的意义5…

儿童全脑九大能力,3-6岁的家长都应该知道

什么是全脑? 人的大脑分左右两个半球,形态虽然相似,功能却各有不同。其中,左脑负责文字、数学、计算、分析、逻辑、顺序、事实和记忆,掌管右侧肢体的感觉和运动;右脑则负责颜色、音乐、想象、韵律、感觉、…

其它 Composition API

1.shallowReactive 与 shallowRef shallowReactive:只处理对象最外层属性的响应式(浅响应式)。 shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。 什么时候使用? 如果有一个对象数据,结构比较深, …

vue-print-nb使用

下载 pnpm add vue-print-nb --save 全局注册&#xff0c;使用插件的注册方式 或 局部注册自定义指令 import print from vue-print-nb directives: {print } 绑定到点击按钮上 <button v-print"content">Print!</button> 设置配置项-常用 id和popTi…

总结:NodeJS

一、介绍Nodejs就像是Java中的JVM&#xff0c;是js的运行环境。nodejs不是一个js框架&#xff0c;千万不要认为是类似jquery的框架。nodejs的作用和jvm的一样一样的&#xff0c;也是js的运行环境&#xff0c;不管你是什么操作系统&#xff0c;只要安装对应版本的nodejs&#xf…

华为OD机试真题 用 C++ 实现 - 字符串加密 | 多看题,提高通过率

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

angular

1. angular获取不到DOM结点 angular中的ngOnInit钩子函数获取不到DOM节点&#xff1b; 这个钩子函数中&#xff0c;表示组件和指令初始化完成&#xff0c;并不是真正的DOM加载完成&#xff1b; 所以这时候需要利用另外一个钩子函数ngAfterViewInit()&#xff0c;是在视图加载完…

界面组件Kendo UI for Angular——让网格数据信息显示更全面

Kendo UI致力于新的开发&#xff0c;来满足不断变化的需求&#xff0c;通过React框架的Kendo UI JavaScript封装来支持React Javascript框架。Kendo UI for Angular是专用于Angular开发的专业级Angular组件&#xff0c;telerik致力于提供纯粹的高性能Angular UI组件&#xff0c…

Leetcode之消失的数字轮转数组

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、消失的数字一、消失的数字 二、旋转数组 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、消失的数字 这题找出消失的一个数字&#…

(二十三)、实现评论功能(3)【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】

1&#xff0c;删除评论的样式和实现逻辑 1.1 添加删除评论的样式 在comment-item组件中&#xff1a; <view class"username">{{giveName(item)}}<text class"iconfont icon-a-43-guanbi" click.stop"delComment"></text><…

【总结】python3启动web服务引发的一系列问题

背景 在某行的实施项目&#xff0c;需要使用python3环境运行某些py脚本。 由于行内交付的机器已自带python3 &#xff0c;没有采取自行安装python3&#xff0c;但是运行python脚本时报没有tornado module。 错误信息 ModuleNotFoundError&#xff1a;No module named ‘torn…

计算机网络第3章(数据链路层)学习笔记

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…