非本地主机下如何获取Google API refresh_token以实现文件上传
问题描述
本地测试(http://localhost:3000/)时,已成功配置refresh_token和access_token生成,表单附件可正常上传至Google Drive,相关代码如下:
import formidable from 'formidable'; import fs from 'fs'; import { google } from 'googleapis' const stream = require('stream'); const CLIENT_ID = process.env.CLIENT_ID const CLIENT_SECRET = process.env.CLIENT_SECRET const REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob" let REFRESH_TOKEN = process.env.REFRESH_TOKEN let ACCESS_TOKEN = process.env.ACCESS_TOKEN let bodyContent = `client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&grant_type=refresh_token&refresh_token=${REFRESH_TOKEN}`; const getTokens = () => { fetch("https://oauth2.googleapis.com/token", { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: bodyContent, }) .then(response => { if (response.ok) { console.log(response) return response.json(); } else { throw new Error('Ошибка при выполнении запроса'); } }) .then(data => { console.log(data.access_token) ACCESS_TOKEN = data.access_token; }) .catch(error => { console.error('Произошла ошибка:', error); }); } getTokens() const auth = new google.auth.OAuth2({ CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, }) auth.setCredentials({ refresh_token: REFRESH_TOKEN, access_token: ACCESS_TOKEN }) const drive = google.drive({ version: 'v3', auth }); async function uploadFiles(file) { const fileContents = fs.readFileSync(`public/${file.name}`) const bufferStream = new stream.PassThrough() bufferStream.end(fileContents) try { const response = await drive.files.create({ media: { mimeType: 'application/pdf', body: bufferStream }, requestBody: { name: file.name, parents: ["1WZj-6PV6sschMMBUFZax-O8ImCqTE7uC"] }, fields: 'id,name' }) console.log(response.data) return response.data.id; } catch (error) { console.error(error); } } async function sendDataToGoogleSheets(body, url) { const data = JSON.parse(body.formValues); const sheets = google.sheets({ version: 'v4', auth }) const urlLink = url ? `https://drive.google.com/file/d/${url}/view` : "Нет приложенного файла" try { const response = await sheets.spreadsheets.values.append({ spreadsheetId: "1AR32ZlUSU03QHPwRV64LyPmwhIlH5vGmLg60Ouq5mhI", range: "A1:F1", valueInputOption: "USER_ENTERED", requestBody: { values: [ [data.name, data.email, data.phoneNumber, data.comment, data.resumeLink, urlLink] ] } }) return response.data } catch (error) { console.log(error.message) } } const post = async (req, res) => { const form = new formidable.IncomingForm(); form.parse(req, async function (err, fields, files) { let file = files ? files.file : null; try { if (file) { await saveFile(file).catch(console.error); const url = await uploadFiles(file).catch(console.error); await sendDataToGoogleSheets(fields, url).catch(console.error); await removeFile(file.name).catch(console.error); } else { await sendDataToGoogleSheets(fields).catch(console.error); } } catch (error) { console.log('Failed to do shit', error.message); res.status(500).end('Unexpected error'); } if (err) { res.status(err.statusCode || 500).send(err.message); } else { res.status(201).send(''); } }); }; const saveFile = async (file) => { const data = fs.readFileSync(file.path); fs.writeFileSync(`./public/${file.name}`, data, console.log('file saved')); await fs.unlinkSync(file.path); return; }; const removeFile = async (fileName) => { await fs.unlink(`./public/${fileName}`, () => { console.log(`file ${fileName} removed`); }); return; }; const reqRes = (req, res) => { req.method === 'POST' ? post(req, res) : req.method === 'PUT' ? console.log('PUT') : req.method === 'DELETE' ? console.log('DELETE') : req.method === 'GET' ? console.log('GET') : res.status(404).send(''); }; export default reqRes; export { config };
部署到Vercel后,文件无法上传至Google Drive,Google表格可正常填充但无文件链接。尝试为Vercel域名生成新的refresh_token和access_token,但仅能获取到access_token:
{ "access_token": "ХХХХХ", "expires_in": 3599, "scope": "https://www.googleapis.com/auth/drive", "token_type": "Bearer" }
代码中需要同时设置refresh_token和access_token才能上传文件(auth.setCredentials({ refresh_token: REFRESH_TOKEN, access_token: ACCESS_TOKEN })),但Vercel环境下无法获取refresh_token,请问如何正确获取所需令牌以支持非本地主机使用?
解决方法
1. 核心:授权请求必须带access_type=offline参数
Google OAuth只有在授权阶段指定access_type=offline时,才会返回长期有效的refresh_token。如果之前的授权没加这个参数,只会返回短期access_token,不会生成refresh_token。
2. 重新生成包含refresh_token的令牌
- 构造授权URL,确保包含以下关键参数(替换为你的实际信息):
其中https://accounts.google.com/o/oauth2/v2/auth? client_id=你的CLIENT_ID& redirect_uri=你的Vercel回调地址& response_type=code& scope=https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/spreadsheets& access_type=offline& prompt=consentprompt=consent会强制用户重新授权,确保即使之前授权过也会返回refresh_token。 - 打开该URL完成授权,用返回的授权码交换令牌:
发送POST请求到https://oauth2.googleapis.com/token,表单参数包括:
这次响应里就会包含client_id=你的CLIENT_ID client_secret=你的CLIENT_SECRET code=授权后得到的CODE redirect_uri=你的Vercel回调地址 grant_type=authorization_coderefresh_token。
3. 简化代码中的令牌管理
Google官方的google-auth-library会自动处理access_token的刷新,不需要手动维护:
- 只需传入refresh_token即可,删除手动刷新的
getTokens函数:
当access_token过期时,库会自动用refresh_token获取新的access_token。auth.setCredentials({ refresh_token: REFRESH_TOKEN });
4. 检查Vercel环境变量配置
确保Vercel后台的环境变量REFRESH_TOKEN、CLIENT_ID、CLIENT_SECRET完全正确,无拼写错误或空格。
5. 确认OAuth应用的redirect_uri配置
- 如果是Web应用类型,必须在Google Cloud控制台中添加Vercel域名作为授权的redirect_uri(比如
https://你的Vercel应用域名.vercel.app/callback); - 如果是桌面应用类型,可继续使用
urn:ietf:wg:oauth:2.0:oob,但需确保应用类型设置正确。
内容的提问来源于stack exchange,提问作者Артем
相关产品推荐
相关产品推荐

