You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js服务中利用刷新令牌逻辑处理过期访问令牌的方案

解决Next.js 15服务端API调用中过期访问令牌的自动刷新问题

问题背景

基于Next.js 15开发的项目中,后端独立处理用户认证:前端通过邮箱密码获取访问JWT和刷新JWT并存入Cookie;中间件全局处理受保护路由的令牌校验,过期则调用/refresh刷新令牌,失败跳转登录。但服务端API调用(如PersonalDataService.ts)在令牌过期时无法触发刷新逻辑,导致请求失败。

触发场景:

  • 用户登录后打开受保护路由填写信息
  • 填写过程中访问令牌过期,触发savePersonalData调用
  • 后端返回失败响应,未执行令牌刷新

解决方案

1. 封装可复用的令牌管理工具

创建app/lib/authUtils.ts,集中处理令牌有效性检查、刷新逻辑:

"use server";

import { cookies } from "next/headers";
import { jwtDecode } from "jwt-decode";
import { redirect } from "next/navigation";

// 校验访问令牌是否过期
const isTokenExpired = (token: string): boolean => {
  const decoded = jwtDecode<{ exp: number }>(token);
  return Date.now() >= decoded.exp * 1000;
};

// 获取有效访问令牌,自动刷新过期令牌
export const getValidAccessToken = async (): Promise<string> => {
  const cookieStore = await cookies();
  let accessToken = cookieStore.get("accessToken")?.value;
  const refreshToken = cookieStore.get("refreshToken")?.value;

  // 无访问令牌直接跳转登录
  if (!accessToken) redirect("/login");

  // 令牌未过期,直接返回
  if (!isTokenExpired(accessToken)) return accessToken;

  // 无刷新令牌,跳转登录
  if (!refreshToken) redirect("/login");

  try {
    // 调用后端刷新接口
    const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/refresh`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ refreshToken }),
    });

    if (!response.ok) {
      // 刷新失败,清除无效令牌并跳转登录
      cookieStore.delete("accessToken");
      cookieStore.delete("refreshToken");
      redirect("/login");
    }

    const { newAccessToken } = await response.json();
    // 更新Cookie中的访问令牌
    cookieStore.set("accessToken", newAccessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: 15 * 60, // 与后端令牌有效期保持一致
    });

    return newAccessToken;
  } catch (error) {
    console.error("令牌刷新失败:", error);
    cookieStore.delete("accessToken");
    cookieStore.delete("refreshToken");
    redirect("/login");
  }
};

2. 修改服务端API调用逻辑

在PersonalDataService.ts中替换直接读取Cookie的逻辑,使用封装的工具函数获取有效令牌:

"use server";

import { cookies } from "next/headers";
import { jwtDecode } from "jwt-decode";
import { getValidAccessToken } from "@/app/lib/authUtils";

export const savePersonalData = async (firstName: string, lastName: string): Promise<boolean> => {
  try {
    // 获取经过校验/刷新的有效访问令牌
    const accessToken = await getValidAccessToken();
    const { sub } = jwtDecode<{ sub: string }>(accessToken);

    const response = await fetch(
      `${process.env.NEXT_PUBLIC_API_URL}/${process.env.NEXT_PUBLIC_API_USER}/${sub}`,
      {
        method: "PATCH",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${accessToken}`,
        },
        body: JSON.stringify({ first_name: firstName.trim(), last_name: lastName.trim() }),
      }
    );

    // 额外处理401情况,防止刷新后的令牌仍无效
    if (response.status === 401) {
      (await cookies()).delete("accessToken");
      (await cookies()).delete("refreshToken");
      redirect("/login");
    }

    return response.ok;
  } catch (error) {
    console.error("保存个人信息失败:", error);
    return false;
  }
};

3. 关键优化点

  • 统一逻辑:所有需要认证的服务端API调用都使用getValidAccessToken,避免重复代码,确保全局一致的令牌处理
  • 错误兜底:刷新失败或令牌无效时,直接清除Cookie并跳转登录,避免无效请求循环
  • 性能提升:仅在令牌过期时触发刷新请求,减少不必要的后端调用;服务端本地校验令牌有效期,无需额外请求
  • 与中间件互补:中间件处理路由进入前的认证,服务端API调用处理请求过程中的令牌过期,覆盖所有认证场景

内容的提问来源于stack exchange,提问作者Amr Gaafer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:30:59