安装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-resolutionspackage.json的scripts中添加:
最后重新执行"preinstall": "npx npm-force-resolutions"npm install。
- Yarn用户,在
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
相关产品推荐
相关产品推荐

