NestJS中JWT双Token机制学习笔记
本次学习围绕NestJS框架下JWT双Token认证机制展开,结合实际代码案例,深入理解双Token机制的设计理念、实现流程、核心代码解析,同时梳理相关的JWT基础、NestJS模块使用、设计模式以及面试高频考点,旨在全面掌握企业级项目中常用的认证方案,解决单Token认证的安全隐患,提升接口安全性和用户体验。通过本次学习,不仅掌握双Token的具体实现,更能理解背后的设计逻辑和技术选型思路,为后续开发企业级接口认证功能奠定基础。
一、前言:从单Token认证的痛点到双Token机制的诞生
在前后端分离项目中,接口认证是保障系统安全的核心环节,JWT(JSON Web Token)作为一种无状态的认证方式,被广泛应用于各类项目中。最初,我接触的是基于jsonwebtoken库的单Token认证模式,通过sign方法生成Token,decoded方法解析Token,实现简单的身份校验。但在实际应用中,单Token认证存在明显的安全隐患和体验缺陷,这也是双Token机制应运而生的核心原因。
1.1 单Token认证的核心问题
单Token认证模式中,仅使用一个Token完成所有接口的身份校验,其核心痛点主要体现在两个方面:
第一,安全性不足。单Token一旦被截获(即使是内部人员误操作截取),攻击者可以利用该Token直接访问系统接口,获取敏感数据或执行非法操作。由于单Token的有效期难以平衡——有效期过长,风险越高;有效期过短,用户需要频繁重新登录,体验极差。
第二,用户体验不佳。若为了提升安全性,将Token有效期设置较短(如15分钟),用户在使用过程中会频繁遇到Token过期,需要重新登录,严重影响使用体验;若将有效期设置较长(如7天),则会大幅增加Token被滥用的风险。
此外,单Token认证中,Token过期后无法无缝刷新,只能强制用户重新登录,进一步降低了用户体验。基于这些痛点,双Token机制(access_token + refresh_token)成为企业级项目中更优的认证选择。
1.2 双Token机制的核心设计理念
双Token机制的核心思路是“拆分职责、分级保障”,通过两个功能相同但有效期、使用场景不同的Token,兼顾系统安全性和用户体验。两个Token均基于JWT标准生成,具备完整的身份校验能力,但其定位和使用场景有明确区分:
-
access_token(访问令牌):短期有效,有效期以分钟为单位(如15分钟),主要用于日常接口访问,承担“临时通行证”的角色。由于有效期短,即使被截获,其滥用的时间窗口极短,安全性较高。
-
refresh_token(刷新令牌):长期有效,有效期以天为单位(如7天),主要用于access_token过期后,无缝刷新获取新的双Token对,承担“备用通行证”的角色。refresh_token仅在access_token过期时使用,使用频率极低,被截获的概率远低于access_token。
双Token机制的核心流程的是:用户登录时,后端生成一对access_token和refresh_token并返回给前端;前端使用access_token访问各类接口,当access_token过期时,前端携带refresh_token请求后端,后端验证refresh_token有效后,重新生成一对新的Token,覆盖旧的Token;当refresh_token过期时,用户需要重新登录,完成身份校验后再次获取新的双Token对。
二、核心知识点铺垫:JWT基础与NestJS相关依赖
在解析双Token机制的代码实现前,先梳理相关的基础知识点,包括JWT的核心原理、NestJS中JWT模块的使用,以及代码中涉及的其他关键依赖和技术,为后续代码拆解做好铺垫。
2.1 JWT核心原理回顾
JWT(JSON Web Token)是一种基于JSON的轻量级身份认证令牌,其核心特点是无状态——后端无需存储Token信息,仅通过密钥即可验证Token的有效性和完整性。JWT的结构由三部分组成,用小数点(.)分隔,分别是Header(头部)、Payload(载荷)、Signature(签名)。
-
Header:用于指定JWT的加密算法(如HS256)和令牌类型,示例:{"alg": "HS256", "typ": "JWT"},该部分会经过Base64编码后作为JWT的第一部分。
-
Payload:用于存储核心的用户身份信息(如用户ID、用户名)和Token的过期时间(exp)等声明,该部分同样经过Base64编码,注意:Payload仅为编码,并非加密,不能存储敏感信息(如密码),示例:{"sub": "123456", "name": "admin", "exp": 1716888000}。
-
Signature:用于验证Token的完整性和合法性,是JWT的核心安全保障。生成签名时,需要使用Header中指定的加密算法,结合密钥(secret),对编码后的Header和Payload进行加密,公式为:Signature = HMACSHA256(base64UrlEncode(Header) + "." + base64UrlEncode(Payload), secret)。
JWT的验证流程:后端接收前端传递的Token后,拆分出Header、Payload、Signature三部分;使用相同的密钥和加密算法,重新对编码后的Header和Payload进行加密,生成新的Signature;对比新生成的Signature与Token中的Signature是否一致,同时校验Payload中的exp(过期时间)是否有效;若两者一致且未过期,则Token有效,否则无效。
2.2 NestJS相关依赖与模块配置
本次代码实现基于NestJS框架,核心依赖包括@nestjs/jwt、bcrypt、prisma等,其中@nestjs/jwt是NestJS官方提供的JWT处理模块,用于生成和验证Token,相较于原生的jsonwebtoken库,更贴合NestJS的依赖注入模式,使用更便捷。
2.2.1 JwtModule模块注册
在NestJS中,使用JWT功能前,需要先在对应的模块(如AuthModule)中注册JwtModule,通过register方法配置JWT的核心参数(如密钥secret)。代码如下:
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt'; // 引入jwt模块
@Module({
imports: [
JwtModule.register({ // register是jwt模块的方法,用来注册jwt模块
secret: process.env.TOKEN_SECRET // 从环境变量中读取密钥,保障安全性
}),
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
核心说明:
-
密钥(secret):是JWT生成和验证的核心,必须保密,建议从环境变量(process.env.TOKEN_SECRET)中读取,避免硬编码在代码中,防止密钥泄露。
-
JwtModule.register():用于注册JWT模块,配置全局的JWT参数,注册后,JwtService会被注入到NestJS的依赖注入容器中,可供AuthService等 providers 注入使用。
-
装饰器模式的应用:@Module装饰器是NestJS中模块的核心,本质上是装饰器模式的体现——通过装饰器快速为AuthModule类添加模块配置(imports、controllers、providers),无需手动修改类的内部结构,提升代码的可扩展性和维护性。
2.2.2 其他关键依赖说明
-
bcrypt:用于密码加密和比对,在用户登录时,将前端传递的明文密码与数据库中存储的加密密码进行比对,保障密码存储安全,避免明文密码泄露。
-
PrismaService:Prisma是一种现代的ORM工具,用于操作数据库,PrismaService用于在AuthService中查询用户信息(如根据用户名查询用户),实现与数据库的交互。
-
@nestjs/common:NestJS的核心模块,提供了Controller、Injectable、HttpCode、HttpStatus、UnauthorizedException等装饰器和类,用于构建控制器、服务和处理HTTP请求与异常。
2.3 设计模式补充说明
代码中隐含了多种企业级开发中常用的设计模式,理解这些设计模式,有助于提升代码的设计能力,应对面试中的设计模式相关问题:
-
装饰器模式:NestJS的核心设计模式之一,如@Module、@Controller、@Post、@Injectable等装饰器,用于快速为类、方法添加额外的功能(如模块配置、接口路由、依赖注入标识),无需修改类的内部逻辑,符合“开放-封闭原则”。
-
单例模式:NestJS中的providers(如AuthService、PrismaService、JwtService)默认是单例模式,即整个应用中只存在一个实例,通过依赖注入的方式供其他组件使用,减少资源消耗,提升性能。
-
工厂模式:JwtModule.register()方法本质上是工厂模式的体现,通过调用register方法,传入配置参数,生成并返回一个配置好的JwtModule实例,隐藏了模块的创建细节。
-
其他常见设计模式:观察者模式(如前端的IntersectionObserver)、代理模式(如JavaScript的Proxy)、订阅-发布者模式(如前端的addEventListener),这些设计模式在前后端开发中均有广泛应用,面试中也常被问到。
三、双Token机制核心代码拆解:从登录到Token刷新全流程
双Token机制的实现流程主要分为三个核心环节:用户登录(生成双Token)、Token验证(access_token用于接口访问,refresh_token用于验证身份)、Token刷新(access_token过期后,通过refresh_token获取新的双Token)。以下结合AuthController、AuthService的代码,逐环节拆解实现逻辑。
3.1 控制器(AuthController):接口路由定义
AuthController负责定义认证相关的接口路由,包括登录接口(/auth/login)和Token刷新接口(/auth/refresh),通过依赖注入的方式调用AuthService中的对应方法,处理前端请求并返回结果。代码如下:
import {
Controller,
Post,
Body,
HttpCode, // 自定义状态码
HttpStatus, // 状态码
}from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
import { AuthService } from './auth.service';
// restful 设计风格:一切皆资源,通过method + URL(名词)定义接口,提升可读性
@Controller('auth') // 父路由:/auth
export class AuthController {
// 依赖注入AuthService,无需手动实例化,NestJS自动管理
constructor(private authService: AuthService) {}
// 登录接口:POST /auth/login
@Post('login')
@HttpCode(HttpStatus.OK) // 自定义HTTP状态码为200(默认POST请求返回201)
async login(@Body() loginDto: LoginDto) {
// 调用AuthService的login方法,传入前端传递的登录参数(用户名、密码)
return this.authService.login(loginDto);
}
// Token刷新接口:POST /auth/refresh
@Post('refresh')
@HttpCode(HttpStatus.OK)
async refresh(@Body('refresh_token') refresh_token:string){
// 调用AuthService的refreshToken方法,传入前端传递的refresh_token
return this.authService.refreshToken(refresh_token);
}
}
核心拆解:
-
路由设计:遵循RESTful设计风格,以名词(auth)作为父路由,通过POST方法分别定义login(登录)和refresh(Token刷新)两个子路由,接口路径清晰,直指资源(认证相关操作)。
-
依赖注入:通过构造函数注入AuthService,NestJS会自动从依赖注入容器中获取AuthService的单例实例,无需手动new AuthService(),降低组件间的耦合度,便于后续测试和维护。
-
请求处理:
-
@Body()装饰器:用于获取前端POST请求传递的请求体数据,login接口中,请求体数据会被校验并转换为LoginDto类型(LoginDto用于定义登录参数的格式和校验规则,如用户名、密码的必填项校验)。
-
@Body('refresh_token'):用于直接获取请求体中名为refresh_token的字段,简化代码,无需手动从请求体中解构。
- 状态码设置:@HttpCode(HttpStatus.OK)用于将接口的HTTP状态码设置为200,默认情况下,NestJS中POST请求的状态码为201(Created),此处设置为200,更符合前后端接口交互的常规习惯。
3.2 服务(AuthService):核心业务逻辑实现
AuthService是双Token机制的核心,封装了用户登录、Token生成、Token验证、Token刷新等核心业务逻辑,通过注入JwtService和PrismaService,实现与JWT和数据库的交互。代码如下(逐方法拆解):
3.2.1 登录逻辑(login方法):验证用户身份并生成双Token
async login(loginDto: LoginDto){
const {name, password} = loginDto;
// 1. 根据用户名查询用户是否存在(从数据库中获取用户信息)
const user = await this.prisma.user.findUnique({
where: {
name, // 条件:用户名等于前端传递的name
}
})
// 2. 验证用户是否存在,以及密码是否正确
if(!user || !(await bcrypt.compare(password, user.password))){
// 若用户不存在或密码不匹配,抛出未授权异常,返回错误提示
throw new UnauthorizedException ('用户名或密码错误');
}
// 3. 生成双Token(access_token和refresh_token)
const token = await this.generateToken(user.id.toString(), user.name);
// 4. 返回Token和用户基本信息(不返回敏感信息,如密码)
return {
...token, // 解构token对象,返回access_token和refresh_token
user:{
id: user.id.toString(),
name: user.name,
}
}
}
核心拆解:
步骤1:用户查询。通过PrismaService的findUnique方法,根据用户名(name)从数据库中查询用户信息,findUnique用于查询符合条件的唯一一条数据(此处用户名是唯一索引,确保每个用户名对应一个用户)。
步骤2:身份校验。这是登录接口的核心安全环节,包含两个校验:
-
校验用户是否存在:若user为null,说明用户名不存在,抛出UnauthorizedException异常。
-
校验密码是否正确:使用bcrypt.compare方法,将前端传递的明文密码(password)与数据库中存储的加密密码(user.password)进行比对。bcrypt加密是不可逆的,只能通过compare方法进行比对,确保密码存储安全。若比对失败,同样抛出未授权异常。
步骤3:生成双Token。调用私有方法generateToken,传入用户ID(转换为字符串)和用户名,生成包含access_token和refresh_token的Token对。
步骤4:返回结果。返回Token对和用户的基本信息(仅返回id和name,不返回密码等敏感信息),供前端存储和使用。
注意:抛出的UnauthorizedException异常,会被NestJS的全局异常过滤器捕获,自动返回HTTP 401(Unauthorized)状态码和对应的错误提示,无需手动处理响应格式,符合接口开发规范。
3.2.2 双Token生成逻辑(generateToken方法):Promise.all并发优化
generateToken是AuthService的私有方法(private修饰),用于生成access_token和refresh_token,核心使用Promise.all实现两个Token的并发生成,优化性能,这也是面试中的高频考点。代码如下:
private async generateToken(id: string, name: string){
// 1. 准备JWT的Payload(载荷),存储用户核心身份信息
const payload = {
sub: id, // subject 主题,JWT标准中的关键字段,通常存储用户ID
name // 用户名,用于前端展示或后端简单校验
};
// 2. 使用Promise.all并发生成两个Token,提升性能
const [at,rt] = await Promise.all([
// 生成access_token:短期有效(15分钟)
this.jwtService.signAsync(payload,{
expiresIn: '15m', // 有效期15分钟,降低被滥用的风险
secret: process.env.TOKEN_SECRET // 与注册JwtModule时的密钥一致
}),
// 生成refresh_token:长期有效(7天)
this.jwtService.signAsync(payload,{
expiresIn: '7d', // 有效期7天,平衡用户体验和安全性
secret: process.env.TOKEN_SECRET
})
])
// 3. 返回生成的双Token对
return {
access_token: at,
refresh_token: rt,
}
}
核心拆解:
-
Payload设计:Payload中存储了用户的id(sub字段)和name,sub是JWT标准中规定的关键字段,用于标识Token的主题(通常是用户ID),便于后端解析时快速获取用户身份信息。需要注意的是,Payload不能存储敏感信息(如密码、手机号),因为Payload仅经过Base64编码,可被轻易解码。
-
JwtService.signAsync方法:NestJS提供的异步生成JWT的方法,替代了原生jsonwebtoken库的sign方法,更贴合NestJS的异步编程模式。该方法接收两个参数:
-
第一个参数:Payload对象,存储用户身份信息。
-
第二个参数:配置对象,包含expiresIn(Token有效期)和secret(密钥),其中secret必须与JwtModule.register()中配置的密钥一致,否则后续验证Token时会失败。
- Promise.all并发生成Token:这是该方法的核心,也是面试中的重点,具体说明如下:
-
问题背景:生成Token的过程(signAsync)需要进行加密运算,存在一定的性能开销和时间消耗。如果采用串行生成(先生成access_token,再生成refresh_token),总耗时是两个Token生成时间的总和;而采用并发生成,总耗时是两个Token生成时间中较长的那一个,大幅提升性能。
-
实现逻辑:Promise.all接收一个Promise数组,数组中包含两个signAsync方法的调用(分别生成access_token和refresh_token),Promise.all会等待数组中所有的Promise都执行完成后,将结果按顺序返回,通过数组解构赋值,将两个Token分别赋值给at(access_token)和rt(refresh_token)。
-
面试延伸:面试官常问“Promise.all的使用场景”,此处双Token的并发生成就是典型场景,除此之外,还有NestJS中查询文章列表时,并发查询文章总数(count)和文章列表(list),示例如下:
// NestJS中PostsService查询文章列表的示例
async getPostsList() {
// 并发查询文章总数和文章列表,提升查询性能
const [count, list] = await Promise.all([
this.prisma.post.count(), // 查询文章总数
this.prisma.post.findMany({ // 查询文章列表(可添加分页、排序等条件)
skip: 0,
take: 10
})
]);
return { count, list };
}
该示例中,文章总数和文章列表的查询是两个独立的操作,无需等待一个完成后再执行另一个,使用Promise.all并发查询,可显著减少接口响应时间,提升用户体验。
- Token有效期设计:access_token设置为15分钟,refresh_token设置为7天,这种设计既保障了安全性,又兼顾了用户体验:
-
access_token短期有效:即使被截获,攻击者只有15分钟的时间可以滥用,风险较低;
-
refresh_token长期有效:用户无需频繁登录,只需在7天内有一次Token刷新操作,即可持续使用系统,若7天内未操作,refresh_token过期,用户需要重新登录,保障系统安全。
3.2.3 Token刷新逻辑(refreshToken方法):验证refresh_token并生成新的双Token
当access_token过期时,前端会携带refresh_token请求后端的/refresh接口,后端通过refreshToken方法验证refresh_token的有效性,若有效,则重新生成一对新的双Token,覆盖旧的Token,实现无缝刷新。代码如下:
async refreshToken(rt:string){
try{
// 1. 验证refresh_token的有效性
const payload = await this.jwtService.verifyAsync(rt,{
secret: process.env.TOKEN_SECRET // 与生成Token时的密钥一致
});
console.log(payload,'payload'); // 调试日志,生产环境可移除
// 2. 若refresh_token有效,根据Payload中的用户信息,重新生成双Token
return this.generateToken(payload.sub, payload.name);
}catch(err){
// 3. 若验证失败(Token无效、过期、签名错误等),抛出未授权异常
throw new UnauthorizedException ('Refresh Token 无效,请重新登录');
}
}
核心拆解:
步骤1:refresh_token验证。调用JwtService的verifyAsync方法,验证前端传递的refresh_token的有效性,该方法的验证流程与JWT的标准验证流程一致:
-
解析Token的Header、Payload、Signature三部分;
-
使用相同的密钥(process.env.TOKEN_SECRET)和加密算法,重新生成Signature,与Token中的Signature对比;
-
校验Payload中的exp(过期时间),判断Token是否过期;
-
若所有校验都通过,返回解析后的Payload(包含用户id和name);若校验失败,抛出异常。
步骤2:重新生成双Token。若refresh_token验证有效,从Payload中获取用户的id(payload.sub)和name(payload.name),调用generateToken方法,重新生成一对新的access_token和refresh_token,新的Token对会覆盖前端存储的旧Token,前端后续使用新的access_token访问接口即可。
步骤3:异常处理。使用try/catch块捕获verifyAsync方法抛出的所有异常(如Token格式错误、签名篡改、Token过期、密钥不匹配等),捕获异常后,不暴露具体的失败原因(避免泄露敏感信息),统一抛出UnauthorizedException异常,提示“Refresh Token 无效,请重新登录”,前端接收该提示后,引导用户重新登录。
步骤4:调试日志。console.log(payload,'payload')用于在开发环境中打印解析后的Payload,便于调试,查看用户身份信息是否正确,生产环境中建议移除该日志,避免不必要的日志输出。
关键注意点:refresh_token同样具备完整的JWT验证能力,其生成和验证的逻辑与access_token完全一致,唯一的区别是有效期不同,这也是双Token机制的核心特点——两个Token功能相同,职责不同。
四、双Token机制的前端配合逻辑(补充)
双Token机制的实现,需要前端和后端协同配合,后端负责Token的生成、验证和刷新,前端负责Token的存储、携带和过期处理。结合代码中的设计,补充前端的核心配合逻辑,让整个双Token流程更完整:
4.1 Token存储
前端接收后端返回的access_token和refresh_token后,需要进行存储,常用的存储方式有localStorage、sessionStorage或Cookie,建议使用localStorage(持久化存储),原因如下:
-
access_token:短期有效(15分钟),即使存储在localStorage中,风险较低;
-
refresh_token:长期有效(7天),存储在localStorage中,用户关闭浏览器后再次打开,仍可使用,无需重新登录,提升用户体验。
存储示例(JavaScript):
// 登录成功后,存储Token
const login = async () => {
const res = await axios.post('/auth/login', { name: 'admin', password: '123456' });
localStorage.setItem('access_token', res.data.access_token);
localStorage.setItem('refresh_token', res.data.refresh_token);
};
4.2 接口请求时携带Token
前端通过axios的请求拦截器,为所有需要认证的接口(除登录、注册接口外)自动添加Authorization请求头,携带access_token,供后端验证。代码示例:
import axios from 'axios';
// 创建axios实例
const service = axios.create({
baseURL: '/api',
timeout: 5000
});
// 请求拦截器:添加Token
service.interceptors.request.use(
config => {
// 从localStorage中获取access_token
const token = localStorage.getItem('access_token');
if (token) {
// 添加Authorization请求头,格式:Bearer + Token(空格分隔)
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
4.3 Token过期处理
当access_token过期时,后端会返回HTTP 401状态码,前端通过axios的响应拦截器,捕获该错误,然后携带refresh_token请求刷新接口,获取新的双Token后,重新发起原请求,实现无缝刷新,无需用户手动操作。代码示例:
// 响应拦截器:处理Token过期
service.interceptors.response.use(
response => response.data,
async error => {
const originalRequest = error.config; // 原请求配置
// 捕获401错误,且不是刷新Token接口本身的错误(避免死循环)
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // 标记请求已重试,避免死循环
try {
// 1. 获取localStorage中的refresh_token
const refreshToken = localStorage.getItem('refresh_token');
// 2. 请求刷新Token接口,获取新的双Token
const res = await axios.post('/auth/refresh', { refresh_token: refreshToken });
const { access_token, refresh_token } = res.data;
// 3. 更新localStorage中的Token
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
// 4. 为原请求添加新的access_token,重新发起请求
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return service(originalRequest);
} catch (err) {
// 若refresh_token无效或过期,清除存储的Token,引导用户重新登录
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.href = '/login'; // 跳转到登录页
}
}
return Promise.reject(error);
}
);
核心说明:通过添加originalRequest._retry标记,避免refresh_token无效时,反复发起刷新请求,导致死循环;若refresh_token验证失败,清除localStorage中的Token,跳转到登录页,引导用户重新登录。
五、面试高频考点汇总(重点)
结合本次学习内容,梳理双Token机制相关的面试高频考点,涵盖核心原理、代码实现、性能优化、设计模式等,帮助应对面试中的相关问题:
5.1 JWT相关考点
- 问:JWT的结构是什么?各部分的作用是什么?
答:JWT由Header、Payload、Signature三部分组成,用小数点分隔。Header指定加密算法和Token类型;Payload存储用户身份信息和过期时间等声明,仅编码不加密;Signature用于验证Token的完整性和合法性,通过密钥和加密算法生成,是安全核心。
- 问:JWT的验证流程是什么?
答:后端拆分Token的三部分,使用相同的密钥和加密算法,重新对Header和Payload进行加密,生成新的Signature;对比新Signature与Token中的Signature是否一致,同时校验Payload中的exp是否过期;一致且未过期则Token有效,否则无效。
- 问:JWT为什么是无状态的?无状态的优势是什么?
答:JWT的无状态体现在后端无需存储Token信息,仅通过密钥即可验证Token的有效性,所有用户身份信息都存储在Token的Payload中。优势是减轻后端服务器的存储压力,便于分布式系统部署(多个服务器可共用密钥,无需同步Token信息)。
5.2 双Token机制考点
- 问:为什么要使用双Token机制?单Token机制的痛点是什么?
答:单Token的痛点是安全性不足(易被截获滥用)和用户体验不佳(有效期难以平衡);双Token机制通过access_token(短期,用于接口访问)和refresh_token(长期,用于Token刷新),拆分职责,既降低了Token被滥用的风险,又避免用户频繁重新登录,兼顾安全和体验。
- 问:双Token机制的核心流程是什么?
答:用户登录 → 后端生成双Token并返回;前端携带access_token访问接口 → access_token过期 → 前端携带refresh_token请求刷新接口 → 后端验证refresh_token有效,生成新的双Token → 前端使用新的access_token继续访问;refresh_token过期 → 用户重新登录。
- 问:access_token和refresh_token的区别是什么?有效期为什么这么设计?
答:区别:两者功能相同(均为JWT,可验证身份),但有效期和使用场景不同。access_token短期(15分钟),用于日常接口访问;refresh_token长期(7天),用于Token刷新。有效期设计:access_token短期降低被滥用风险,refresh_token长期提升用户体验,7天有效期平衡安全和体验。
5.3 Promise.all考点
- 问:Promise.all的作用是什么?使用场景有哪些?举两个NestJS中的示例。
答:Promise.all接收一个Promise数组,等待所有Promise执行完成后,按顺序返回结果,用于实现多个异步操作的并发执行,提升性能。
示例1:双Token并发生成,同时调用signAsync生成access_token和refresh_token,减少总耗时。
示例2:文章列表查询,并发查询文章总数(count)和文章列表(list),减少接口响应时间。
- 问:Promise.all的异常处理机制是什么?
答:Promise.all中只要有一个Promise抛出异常,整个Promise.all会立即 reject,返回第一个抛出的异常,其他未执行完成的Promise会被终止。
5.4 NestJS相关考点
- 问:NestJS中如何使用JWT?JwtModule的作用是什么?
答:首先安装@nestjs/jwt依赖,在模块中通过JwtModule.register()注册模块,配置密钥;然后在Service中注入JwtService,使用signAsync生成Token,verifyAsync验证Token。JwtModule的作用是封装JWT的核心功能,提供依赖注入支持,简化JWT的使用。
- 问:NestJS中的依赖注入是什么?代码中如何体现?
答:依赖注入是NestJS的核心,通过将依赖的组件(如AuthService、JwtService)注入到当前组件中,无需手动实例化,降低耦合度。代码中,AuthController的构造函数注入AuthService,AuthService的构造函数注入JwtService和PrismaService,均体现了依赖注入。
- 问:代码中使用了哪些装饰器?各自的作用是什么?
答:@Module:定义模块,配置imports(导入依赖模块)、controllers(控制器)、providers(服务);@Controller:定义控制器,指定父路由;@Post:定义POST接口,指定子路由;@Injectable:标记服务类,使其可被依赖注入;@Body:获取请求体数据;@HttpCode:自定义HTTP状态码。
5.5 安全相关考点
- 问:如何保障JWT的安全性?
答:① 密钥必须保密,建议从环境变量读取,避免硬编码;② access_token设置短期有效期,降低被滥用风险;③ Payload不存储敏感信息(如密码);④ 使用HTTPS协议,防止Token在传输过程中被截获;⑤ 验证Token时,严格校验签名和过期时间。
- 问:refresh_token过期后,为什么需要用户重新登录?
答:refresh_token是长期有效的Token,若refresh_token永久有效,一旦被截获,攻击者可以无限刷新Token,滥用系统资源,因此设置7天有效期,过期后用户重新登录,重新验证身份,保障系统安全。
六、学习总结与反思
通过本次学习,我全面掌握了NestJS中JWT双Token机制的核心原理和实现流程,从单Token的痛点出发,理解了双Token机制“拆分职责、分级保障”的设计理念,同时深入拆解了代码中的每一个核心方法,掌握了JwtModule、PrismaService的使用,以及Promise.all的并发优化场景。
本次学习的重点的是双Token的生成、验证和刷新流程,以及Promise.all的使用场景和面试考点,同时补充了前端的配合逻辑,让整个认证流程更加完整。此外,通过梳理代码中隐含的设计模式,提升了自己的代码设计能力,理解了企业级项目中“高内聚、低耦合”的开发理念——将核心业务逻辑封装在Service中,Controller仅负责接口路由的定义,通过依赖注入实现组件间的交互,便于后续的测试和维护。
反思本次学习,我也发现了一些需要改进的地方:一是对JWT的加密算法(如HS256、RS256)的区别理解不够深入,后续需要进一步学习非对称加密在JWT中的应用;二是对NestJS全局异常过滤器、拦截器的自定义实现掌握不够熟练,后续需要结合实际项目,完善认证流程的异常处理和日志记录;三是对前端Token存储的安全性考虑不够全面,后续需要学习Cookie的secure、httpOnly属性,进一步提升Token存储的安全性。
总体而言,JWT双Token机制是企业级前后端分离项目中最常用的认证方案之一,本次学习不仅掌握了具体的代码实现,更理解了背后的设计逻辑和安全考量,为后续开发企业级接口认证功能提供了坚实的基础。在今后的学习和工作中,我会继续深入研究NestJS框架和JWT相关技术,结合实际项目场景,不断优化认证方案,提升系统的安全性和用户体验。