如何在NestJS中处理401状态码时自动刷新JWT并重试请求
在NestJS中实现JWT自动刷新与请求重试
核心逻辑
可以通过NestJS的**拦截器(Interceptor)**捕获受保护路由返回的401错误,自动调用刷新令牌接口获取新的访问令牌,再重新发起原请求。整个流程无需前端介入,完全在服务端处理。
实现步骤
1. 编写刷新令牌服务
先实现一个负责验证刷新令牌、生成新访问令牌的服务:
@Injectable() export class AuthService { constructor(private readonly jwtService: JwtService) {} async refreshAccessToken(refreshToken: string): Promise<{ accessToken: string }> { // 验证刷新令牌合法性(可结合Redis检查是否被拉黑/过期) const payload = this.jwtService.verify(refreshToken, { secret: process.env.JWT_REFRESH_SECRET }); // 生成新的短期访问令牌 const newAccessToken = this.jwtService.sign( { userId: payload.userId }, { expiresIn: '15m', secret: process.env.JWT_ACCESS_SECRET } ); return { accessToken: newAccessToken }; } }
2. 实现自动刷新拦截器
创建拦截器捕获401响应,执行刷新逻辑并重试请求:
@Injectable() export class TokenRefreshInterceptor implements NestInterceptor { constructor(private readonly authService: AuthService) {} async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); return next.handle().pipe( catchError(async (error) => { // 仅处理携带刷新令牌的401请求 if (error.status === 401 && request.cookies?.refreshToken) { try { // 获取新的访问令牌 const { accessToken } = await this.authService.refreshAccessToken(request.cookies.refreshToken); // 更新请求头的Authorization字段 request.headers.authorization = `Bearer ${accessToken}`; // 将新令牌写入Cookie(也可返回给前端) response.cookie('accessToken', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 15 * 60 * 1000 // 与访问令牌有效期一致 }); // 重新发起原请求 const httpServer = context.switchToHttp().getHttpServer(); return httpServer.handle(context); } catch (refreshError) { // 刷新失败,直接返回401要求重新登录 throw new UnauthorizedException('Refresh token invalid'); } } // 非目标错误,直接抛出原异常 throw error; }) ); } }
3. 配置拦截器与守卫的执行顺序
NestJS中守卫(如JwtAuthGuard)会先于拦截器执行,所以要确保拦截器能捕获守卫抛出的401错误。可以全局注册拦截器:
@Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: TokenRefreshInterceptor, }, ], }) export class AppModule {}
也可以在特定受保护路由上单独使用:
@Controller('user') @UseInterceptors(TokenRefreshInterceptor) export class UserController { @Get('profile') @UseGuards(JwtAuthGuard) getProfile(@Request() req) { return req.user; } }
4. 关键注意事项
- 避免重复刷新:可以用Redis或内存缓存记录正在刷新的用户ID,防止同一用户并发触发多次刷新请求。
- 重试边界控制:只重试一次,避免因刷新逻辑异常导致死循环。
- 安全性保障:刷新令牌必须存储在
HttpOnly+Secure的Cookie中,防止XSS攻击;定期轮换刷新令牌,降低泄露风险。 - 过期处理:若刷新令牌已过期,直接返回401,引导用户重新登录。
内容的提问来源于stack exchange,提问作者Arshia Arsh1a Faraji
相关产品推荐
相关产品推荐

