如何在Node.js后端基于Passport.js实现refresh token自动刷新流程
实现方案
核心思路是封装统一的Google API请求实例,通过拦截器自动处理token刷新,完全不需要在每个业务路由里写重复逻辑。
第一步:先修复现有Passport策略的小缺陷
当前你写的策略里,老用户登录时没有更新token,后续刷新可能用到过期的refresh token,调整下逻辑:
// 原来的passport.use里的try块调整 try { let user = await User.findOne({ googleId: profile.id }); if(user) { // 老用户登录也要更新最新的token和refresh token await Key.findOneAndUpdate( { googleId: user.googleId, keyProvider: 'Google' }, { token: accessToken, refreshToken: refreshToken }, { upsert: true } ); done(null, user); } else { user = await User.create(newUser); await Key.create({ googleId: user.googleId, keyProvider: 'Google', token: accessToken, refreshToken: refreshToken }); done(null, user); } } catch (err) { console.error(err); done(err); }
第二步:编写通用刷新token工具函数
单独建个文件比如 utils/googleAuth.js,写刷新逻辑:
const axios = require('axios'); const Key = require('../models/Key'); // 导入你的Key模型 // 刷新access token async function refreshGoogleToken(googleId) { // 从数据库查对应refresh token const keyRecord = await Key.findOne({ googleId, keyProvider: 'Google' }); if (!keyRecord || !keyRecord.refreshToken) { throw new Error('未找到对应refresh token,请重新登录'); } try { const res = await axios.post('https://oauth2.googleapis.com/token', { client_id: process.env.GOOGLE_CLIENT_ID, client_secret: process.env.GOOGLE_CLIENT_SECRET, refresh_token: keyRecord.refreshToken, grant_type: 'refresh_token' }); const newAccessToken = res.data.access_token; // 更新数据库里的access token await Key.findOneAndUpdate( { googleId, keyProvider: 'Google' }, { token: newAccessToken } ); return newAccessToken; } catch (err) { // refresh token也失效了,需要用户重新授权 await Key.findOneAndDelete({ googleId, keyProvider: 'Google' }); throw new Error('refresh token失效,请重新登录'); } }
第三步:封装带自动刷新的axios实例
还是在 utils/googleAuth.js 里加这个实例,用响应拦截器处理401自动刷新重试:
// 用来避免多个请求同时触发多次刷新 let refreshingPromise = null; const googleApiClient = axios.create({ baseURL: 'https://www.googleapis.com', headers: { 'Content-Type': 'application/json' } }); // 响应拦截器 googleApiClient.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 只有401错误、且不是重试请求的时候才走刷新逻辑 if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; // 已经在刷新的话就等刷新完成 if (!refreshingPromise) { // 从请求的自定义参数里拿googleId,后面发请求的时候要传 refreshingPromise = refreshGoogleToken(originalRequest.googleId); } try { const newAccessToken = await refreshingPromise; // 更新请求的token originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return googleApiClient(originalRequest); } catch (err) { return Promise.reject(err); } finally { refreshingPromise = null; } } return Promise.reject(error); } ); module.exports = { googleApiClient, refreshGoogleToken };
第四步:业务路由直接调用即可
原来的路由代码几乎不用改,直接用封装好的实例,传入当前用户的googleId就行:
const { googleApiClient } = require('../utils/googleAuth'); router.get('/list', async (req, res, next) => { console.log('list called'); try { // 假设你用了passport的session,req.user里有googleId const userKey = await Key.findOne({ googleId: req.user.googleId, keyProvider: 'Google' }); const result = await googleApiClient.get('/youtube/v3/subscriptions', { params: { channelId: '[someid]' }, // 把当前用户的googleId传进去,供拦截器用 googleId: req.user.googleId, headers: { // 第一次请求用数据库里存的token Authorization: `Bearer ${userKey.token}` } }); console.log(result.data); res.json(result.data); } catch (err) { // 这里捕获的错误就是刷新失败的错误,直接返回给前端提示重新登录就行 next(err); } });
如果觉得每次查token麻烦,可以再封装个请求方法,自动根据传入的googleId查token加到头里,进一步简化业务代码。
内容的提问来源于stack exchange,提问作者nils
相关产品推荐
相关产品推荐

