React中Google OAuth页面刷新后重新初始化gapi的方法
问题分析
页面刷新后,localStorage中仍保留accessToken和userDetails,但gapi客户端未重新初始化,也未将已有的accessToken绑定到gapi授权上下文,导致后续调用日历API时缺少有效连接。同时当前代码未处理gapi加载就绪的时机,可能在gapi未初始化完成时执行相关操作。
修复方案
1. 确保gapi加载完成后初始化
添加专门的useEffect处理gapi客户端加载,就绪后再设置API密钥和已保存的accessToken。
2. 页面刷新时重新绑定accessToken到gapi
读取localStorage的accessToken后,不仅更新组件状态,还要通过gapi.client.setToken()将token绑定到gapi客户端,恢复授权上下文。
3. 同步token刷新后的gapi状态
刷新accessToken成功后,立即更新gapi.client的token,保证后续API调用有效性。
4. 规范refreshToken存储
refreshToken仅在首次授权时返回,建议存储到后端而非前端localStorage,避免安全风险。
修改后的完整代码
import { useState, useEffect } from "react"; import { gapi } from "gapi-script"; import axios from "axios"; import { useSelector } from "react-redux"; // 定义日历API所需权限范围 const SCOPES = "https://www.googleapis.com/auth/calendar.readonly"; export const useGoogleSignIn = () => { const [googleAccessToken, setGoogleAccessToken] = useState(null); const [userDetails, setUserDetails] = useState(null); const { hotCredintials } = useSelector((state) => state.hotCredintials); // 建议从安全配置或Redux中获取,避免硬编码 const apiKey = ""; const clientID = ""; let refreshToken = ""; // 初始化gapi客户端,页面刷新时恢复授权 useEffect(() => { const initGapi = async () => { await gapi.load("client", () => { gapi.client.setApiKey(apiKey); const savedToken = localStorage.getItem("googleAccessToken"); if (savedToken) { const parsedToken = JSON.parse(savedToken); gapi.client.setToken({ access_token: parsedToken }); setGoogleAccessToken(prev => ({ ...prev, access_token: parsedToken })); } }); }; initGapi(); }, []); // 获取用户信息 useEffect(() => { if (!googleAccessToken?.access_token) return; fetch(`https://www.googleapis.com/oauth2/v1/userinfo?access_token=${googleAccessToken.access_token}`) .then(response => response.json()) .then(content => setUserDetails(content)) .catch(err => console.error("获取用户信息失败:", err)); }, [googleAccessToken]); // Google登录流程 const onGoogleSignIn = () => { const timestampInMilliseconds = new Date().getTime(); const client = window.google.accounts.oauth2.initTokenClient({ client_id: hotCredintials.find(({ credentialId }) => credentialId === "3")?.credentialValue, scope: SCOPES, callback: (tokenResponse) => { if (tokenResponse?.access_token) { setGoogleAccessToken(tokenResponse); localStorage.setItem("googleAccessToken", JSON.stringify(tokenResponse.access_token)); localStorage.setItem("googleAccessTokenExpire", JSON.stringify(timestampInMilliseconds)); // 保存refreshToken,建议存储到后端 if (tokenResponse.refresh_token) { refreshToken = tokenResponse.refresh_token; } // 绑定token到gapi客户端 gapi.client.setToken(tokenResponse); } }, }); client.requestAccessToken(); }; // Google登出流程 const onGoogleSignOut = () => { window.google.accounts.id.disableAutoSelect(); if (googleAccessToken?.access_token) { window.google.accounts.oauth2.revoke(googleAccessToken.access_token); } gapi.client.setToken(null); setGoogleAccessToken(null); setUserDetails(null); localStorage.removeItem("googleAccessToken"); localStorage.removeItem("googleAccessTokenExpire"); }; // 自动刷新accessToken useEffect(() => { if (!googleAccessToken?.access_token) return; const intervalId = setInterval(() => { const expireTime = Number(localStorage.getItem("googleAccessTokenExpire")); const timeLimit = expireTime + 3580 * 1000; // 提前20秒刷新 const timeNow = new Date().getTime(); if (timeNow > timeLimit) { const savedRefreshToken = refreshToken || localStorage.getItem("googleRefreshToken"); if (!savedRefreshToken) return; axios.post(`https://oauth2.googleapis.com/token`, { client_id: clientID, client_secret: "", grant_type: "refresh_token", refresh_token: savedRefreshToken }) .then(res => { const newToken = { ...googleAccessToken, access_token: res.data.access_token, expires_in: res.data.expires_in, token_type: res.data.token_type, scope: res.data.scope }; setGoogleAccessToken(newToken); localStorage.setItem("googleAccessToken", JSON.stringify(res.data.access_token)); localStorage.setItem("googleAccessTokenExpire", JSON.stringify(new Date().getTime())); // 同步更新gapi客户端的token gapi.client.setToken(newToken); }) .catch(err => console.error("刷新token失败:", err)); } }, 10000); // 每10秒检查一次 return () => clearInterval(intervalId); }, [googleAccessToken]); return { onGoogleSignIn, onGoogleSignOut, userDetails, googleAccessToken }; };
关键修复点说明
- gapi初始化时机:通过
gapi.load("client")确保gapi客户端加载完成后再执行配置操作,避免未就绪时的错误。 - 页面刷新恢复授权:在gapi初始化阶段读取localStorage的accessToken,通过
gapi.client.setToken()绑定到gapi,恢复与Google服务的连接。 - token刷新同步:每次刷新accessToken后,立即更新gapi.client的token,保证后续日历API调用的授权有效性。
- 安全优化:refreshToken建议存储到后端,避免前端存储带来的泄露风险。
内容的提问来源于stack exchange,提问作者aarriiaann
相关产品推荐
相关产品推荐

