如何通过Google Identity Services让浏览器保留登录状态?
问题:Google Identity Services API刷新页面后需重新登录,如何保留登录状态?
我正在使用Google Identity Services JavaScript API访问个人Google数据,代码参考文档的Implicit flow examples > The new way > GAPI async/await章节,在http://localhost:xxxx环境运行,且已将该域名及端口添加到相关凭证中。
目前功能正常:
- 登录后可查看个人头像及名称;
- 登录后可访问预期的Calendar数据。
但每次刷新页面后都需要重新登录,想知道有没有办法让浏览器保留登录状态,避免反复点击授权弹窗中的用户名?
原代码如下:
<!DOCTYPE html> <html> <head></head> <body> <h1>GAPI with GIS async/await</h1> <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br> <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button> <script> const gapiLoadPromise = new Promise((resolve, reject) => { gapiLoadOkay = resolve; gapiLoadFail = reject; }); const gisLoadPromise = new Promise((resolve, reject) => { gisLoadOkay = resolve; gisLoadFail = reject; }); var tokenClient; (async () => { document.getElementById("showEventsBtn").style.visibility="hidden"; document.getElementById("revokeBtn").style.visibility="hidden"; // First, load and initialize the gapi.client await gapiLoadPromise; await new Promise((resolve, reject) => { // NOTE: the 'auth2' module is no longer loaded. gapi.load('client', {callback: resolve, onerror: reject}); }); await gapi.client.init({ // NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient(). }) .then(function() { // Load the Calendar API discovery document. gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'); }); // Now load the GIS client await gisLoadPromise; await new Promise((resolve, reject) => { try { tokenClient = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_CLIENT_ID', scope: 'https://www.googleapis.com/auth/calendar.readonly', prompt: 'consent', callback: '', // defined at request time in await/promise scope. }); resolve(); } catch (err) { reject(err); } }); document.getElementById("showEventsBtn").style.visibility="visible"; document.getElementById("revokeBtn").style.visibility="visible"; })(); async function getToken(err) { if (err.result.error.code == 401 || (err.result.error.code == 403) && (err.result.error.status == "PERMISSION_DENIED")) { // The access token is missing, invalid, or expired, prompt for user consent to obtain one. await new Promise((resolve, reject) => { try { // Settle this promise in the response callback for requestAccessToken() tokenClient.callback = (resp) => { if (resp.error !== undefined) { reject(resp); } // GIS has automatically updated gapi.client with the newly issued access token. console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken())); resolve(resp); }; tokenClient.requestAccessToken(); } catch (err) { console.log(err) } }); } else { // Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on. throw new Error(err); } } function showEvents() { // Try to fetch a list of Calendar events. If a valid access token is needed, // prompt to obtain one and then retry the original request. gapi.client.calendar.events.list({ 'calendarId': 'primary' }) .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse))) .catch(err => getToken(err)) // for authorization errors obtain an access token .then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' })) .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse))) .catch(err => console.log(err)); // cancelled by user, timeout, etc. } function revokeToken() { let cred = gapi.client.getToken(); if (cred !== null) { google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)}); gapi.client.setToken(''); } } </script> <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoadOkay()" onerror="gapiLoadFail(event)"></script> <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoadOkay()" onerror="gisLoadFail(event)"></script> </body> </html>
解决方案
要实现页面刷新后自动保留登录状态,需要调整3个关键部分:
1. 调整授权弹窗触发策略
将initTokenClient中的prompt参数从'consent'改为空字符串'',这样Google GIS会优先尝试静默授权,仅在token失效或权限变更时才弹出确认框:
tokenClient = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_CLIENT_ID', scope: 'https://www.googleapis.com/auth/calendar.readonly', prompt: '', // 修改此处,启用静默授权 callback: '', });
2. 持久化存储Token到本地
获取到有效token后,将其存入localStorage;页面加载时优先从本地读取并恢复token,避免每次重新请求:
- 在
getToken函数的callback中添加存储逻辑:
tokenClient.callback = (resp) => { if (resp.error !== undefined) { reject(resp); } const token = gapi.client.getToken(); localStorage.setItem('google_oauth_token', JSON.stringify(token)); // 存储token console.log('gapi.client access token: ' + JSON.stringify(token)); resolve(resp); };
- 在页面初始化时恢复token:
await gapi.client.init({}) .then(function() { // 从localStorage恢复token const savedToken = localStorage.getItem('google_oauth_token'); if (savedToken) { gapi.client.setToken(JSON.parse(savedToken)); } gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'); });
3. 页面加载时自动验证并刷新Token
在初始化完成后,主动尝试请求一次数据,触发自动静默授权流程,无需用户手动点击按钮:
document.getElementById("showEventsBtn").style.visibility="visible"; document.getElementById("revokeBtn").style.visibility="visible"; // 页面加载完成后自动尝试验证登录状态 showEvents();
完整修改后的代码
<!DOCTYPE html> <html> <head></head> <body> <h1>GAPI with GIS async/await</h1> <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br> <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button> <script> const gapiLoadPromise = new Promise((resolve, reject) => { gapiLoadOkay = resolve; gapiLoadFail = reject; }); const gisLoadPromise = new Promise((resolve, reject) => { gisLoadOkay = resolve; gisLoadFail = reject; }); var tokenClient; (async () => { document.getElementById("showEventsBtn").style.visibility="hidden"; document.getElementById("revokeBtn").style.visibility="hidden"; // First, load and initialize the gapi.client await gapiLoadPromise; await new Promise((resolve, reject) => { gapi.load('client', {callback: resolve, onerror: reject}); }); await gapi.client.init({}) .then(function() { // 从localStorage恢复保存的token const savedToken = localStorage.getItem('google_oauth_token'); if (savedToken) { gapi.client.setToken(JSON.parse(savedToken)); } // Load the Calendar API discovery document. gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'); }); // Now load the GIS client await gisLoadPromise; await new Promise((resolve, reject) => { try { tokenClient = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_CLIENT_ID', scope: 'https://www.googleapis.com/auth/calendar.readonly', prompt: '', // 启用静默授权,避免每次弹窗 callback: '', }); resolve(); } catch (err) { reject(err); } }); document.getElementById("showEventsBtn").style.visibility="visible"; document.getElementById("revokeBtn").style.visibility="visible"; // 页面加载完成后自动验证登录状态 showEvents(); })(); async function getToken(err) { if (err.result.error.code == 401 || (err.result.error.code == 403) && (err.result.error.status == "PERMISSION_DENIED")) { await new Promise((resolve, reject) => { try { tokenClient.callback = (resp) => { if (resp.error !== undefined) { reject(resp); } const token = gapi.client.getToken(); // 将token存入localStorage持久化 localStorage.setItem('google_oauth_token', JSON.stringify(token)); console.log('gapi.client access token: ' + JSON.stringify(token)); resolve(resp); }; tokenClient.requestAccessToken(); } catch (err) { console.log(err) } }); } else { throw new Error(err); } } function showEvents() { gapi.client.calendar.events.list({ 'calendarId': 'primary' }) .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse))) .catch(err => getToken(err)) .then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' })) .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse))) .catch(err => console.log(err)); } function revokeToken() { let cred = gapi.client.getToken(); if (cred !== null) { google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)}); gapi.client.setToken(''); // 清除本地存储的token localStorage.removeItem('google_oauth_token'); } } </script> <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoadOkay()" onerror="gapiLoadFail(event)"></script> <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoadOkay()" onerror="gisLoadFail(event)"></script> </body> </html>
内容的提问来源于stack exchange,提问作者AGamePlayer
相关产品推荐
相关产品推荐

