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

安装jsonwebtoken后Node.js上传文件至Google Drive失败求助

解决Google Drive上传时的invalid_grant JWT时间异常问题

问题场景

原本基于Node.js+multer+Google Drive API的文件上传功能运行正常,安装jsonwebtoken包后,前端上传文件时触发以下错误:

Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim.

相关代码

后端upload.js

const multer = require("multer");
const { google } = require("googleapis");
const fs = require("fs");
const path = require("path");
const router = require("express").Router();
const FOLDERS = require("../utils/folder.drive");
const upload = multer({ dest: "uploads/" });

// Load the service account credentials
const KEYFILEPATH = path.join(__dirname, "../json/drive-credentials.json");
const SCOPES = ["https://www.googleapis.com/auth/drive"];

let drive;

// Initialize Google Drive API
const initializeDrive = async () => {
  try {
    const auth = new google.auth.GoogleAuth({
      keyFile: KEYFILEPATH,
      scopes: SCOPES,
    });
    drive = google.drive({ version: "v3", auth });
  } catch (error) {
    console.error("Error initializing Google Drive API:", error);
    throw new Error("Failed to initialize Google Drive API");
  }
};

// Ensure the API is initialized before any route is used
initializeDrive();

// Upload file to Google Drive
const uploadFile = async (file, folderId) => {
  if (!drive) {
    throw new Error("Google Drive API not initialized");
  }

  const fileMetadata = {
    name: file.originalname,
    parents: [FOLDERS[folderId] || FOLDERS.test],
  };
  const media = {
    mimeType: file.mimetype,
    body: fs.createReadStream(file.path),
  };
  try {
    const response = await drive.files.create({
      resource: fileMetadata,
      media: media,
      fields: "id",
    });
    return response.data.id;
  } catch (error) {
    console.error("Error uploading file:", error);
    throw new Error("Failed to upload file");
  }
}

router.route("/").post(upload.single("file"), async (req, res) => {
  // Get the folder id
  const { folderId } = req.body;
  // Try uploading
  try {
    const fileId = await uploadFile(req.file, folderId);
    fs.unlinkSync(req.file.path); // Delete the file from the server after uploading
    res.status(200).send({ success: true, fileId: fileId });
  } catch (error) {
    // Send error response if something goes wrong
    res.status(500).send({ success: false, error: error.message });
  }
});

module.exports = router;

前端fileUpload.js

import axios from "axios";
import API from "../config/api.config";

const uploadFile = async (file, folderId) => {
  const formData = new FormData();
  formData.append("file", file);
  formData.append("folderId", folderId);

  return await axios
    .post(API.url + API.endpoints.upload, formData)
    .then((res) => res.data)
    .then((data) => {
      return { success: true, data };
    })
    .catch((error) => {
      return { success: false, error };
    });
};

export default uploadFile;

完整错误日志

Error uploading file: GaxiosError: invalid_grant: Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim.
    at Gaxios._request (C:\devfreeguy\projects\web\grajos\backend\node_modules\gaxios\build\src\gaxios.js:142:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async GoogleToken._GoogleToken_requestToken (C:\devfreeguy\projects\web\grajos\backend\node_modules\gtoken\build\src\index.js:241:19)
    at async GoogleToken._GoogleToken_getTokenAsync (C:\devfreeguy\projects\web\grajos\backend\node_modules\gtoken\build\src\index.js:160:16)
    at async JWT.refreshTokenNoCache (C:\devfreeguy\projects\web\grajos\backend\node_modules\google-auth-library\build\src\auth\jwtclient.js:173:23)
    at async JWT.getRequestMetadataAsync (C:\devfreeguy\projects\web\grajos\backend\node_modules\google-auth-library\build\src\auth\oauth2client.js:333:17)
    at async JWT.requestAsync (C:\devfreeguy\projects\web\grajos\backend\node_modules\google-auth-library\build\src\auth\oauth2client.js:418:23)
    at async uploadFile (C:\devfreeguy\projects\web\grajos\backend\routes\upload.js:47:22)
    at async C:\devfreeguy\projects\web\grajos\backend\routes\upload.js:64:20 {
  config: {
    method: 'POST',
    url: 'https://www.googleapis.com/oauth2/v4/token',
    data: {
      grant_type: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      assertion: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.'
    },
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/9.14.0',
      'x-goog-api-client': 'gl-node/20.17.0',
      Accept: 'application/json'
    },
    responseType: 'json',
    retryConfig: {
      httpMethodsToRetry: [Array],
      currentRetryAttempt: 0,
      retry: 3,
      noResponseRetries: 2,
      retryDelayMultiplier: 2,
      timeOfFirstRequest: 1725367689228,
      totalTimeout: 9007199254740991,
      maxRetryDelay: 9007199254740991,
      statusCodesToRetry: [Array]
    },
    paramsSerializer: [Function: paramsSerializer],
    body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    validateStatus: [Function: validateStatus],
    errorRedactor: [Function: defaultErrorRedactor]
  },
  response: {
    config: {
      method: 'POST',
      url: 'https://www.googleapis.com/oauth2/v4/token',
      data: [Object],
      headers: [Object],
      responseType: 'json',
      retryConfig: [Object],
      paramsSerializer: [Function: paramsSerializer],
      body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      validateStatus: [Function: validateStatus],
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: {
      error: 'invalid_grant',
      error_description: 'Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim.'
    },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'cache-control': 'private',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=UTF-8',
      date: 'Tue, 03 Sep 2024 13:55:16 GMT',
      server: 'scaffolding on HTTPServer2',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    },
    status: 400,
    statusText: 'Bad Request',
    request: { responseURL: 'https://www.googleapis.com/oauth2/v4/token' }
  },
  error: undefined,
  status: 400,
  [Symbol(gaxios-gaxios-error)]: '6.7.1'
}

解决方案

1. 修复依赖版本冲突

jsonwebtoken与google-auth-library依赖的JWT处理库(如jws)版本不兼容,导致生成的JWT时间字段异常。

  • 清理现有依赖后重新安装:
    rm -rf node_modules package-lock.json
    npm install
    
  • 若问题仍存在,强制指定兼容版本:
    • Yarn用户,在package.json中添加:
      "resolutions": {
        "jsonwebtoken": "^9.0.2",
        "jws": "^4.0.0"
      }
      
    • npm用户,先安装版本强制工具:
      npm install -g npm-force-resolutions
      
      再在package.json的scripts中添加:
      "preinstall": "npx npm-force-resolutions"
      
      最后重新执行npm install。

2. 确保Google Drive API初始化完成

原代码中initializeDrive()是异步执行,但路由可能在初始化完成前被调用,导致Token生成逻辑异常。

  • 修改upload.js,导出初始化函数:
    module.exports = { router, initializeDrive };
    
  • 在主入口文件(如app.js)中等待初始化完成后再挂载路由:
    const express = require("express");
    const { router: uploadRouter, initializeDrive } = require("./routes/upload");
    const app = express();
    
    async function startServer() {
      await initializeDrive();
      app.use("/upload", uploadRouter);
      app.listen(3000, () => console.log("Server running on port 3000"));
    }
    
    startServer();
    

3. 校验服务器系统时间

Google对JWT的签发时间(iat)和过期时间(exp)校验严格,服务器时间与标准时间偏差超过5分钟会触发该错误。

  • 同步服务器系统时间到NTP服务器,确保时间准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:42:04