You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

页面刷新后Google登录需重新授权问题求助

问题描述
  • 点击Authorize按钮可完成Google登录,但刷新页面时会再次提示登录,希望实现用户主动登出前保持登录状态,当前使用JavaScript版Calendar API插入日历事件。
  • 使用Google Calendar API时,页面重新加载无法保持登录状态,未找到相关解决方案。

解决方案:保持Google Calendar API登录状态跨页面刷新

核心思路是持久化存储认证token,并在页面加载时自动恢复登录状态,同时处理token过期的情况。以下是具体实现:

关键修改点

  1. 登录成功后将token存入localStorage实现持久化
  2. 页面初始化API客户端后,读取本地token并验证有效性
  3. 用户登出时同步清除本地存储的token

修改后的完整代码

<!DOCTYPE html>
<html>
   <head>
      <title>Google Calendar API Quickstart</title>
      <meta charset="utf-8" />
   </head>
   <body>
      <p>Google Calendar API Quickstart</p>
      <!--Add buttons to initiate auth sequence and sign out-->
      <button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
      <button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>
      <button id="addto_cal" onclick="addto_cal()">Add to calendar</button>
      <pre id="content" style="white-space: pre-wrap;"></pre>
      <script type="text/javascript">
         /* exported gapiLoaded */
         /* exported gisLoaded */
         /* exported handleAuthClick */
         /* exported handleSignoutClick */
         
         // TODO(developer): Set to client ID and API key from the Developer Console
         const CLIENT_ID = 'my_client_id';
         const API_KEY = 'my_api_key';
         
         // Discovery doc URL for APIs used by the quickstart
         const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';
         
         // Authorization scopes required by the API; multiple scopes can be
         // included, separated by spaces.
         const SCOPES = 'https://www.googleapis.com/auth/calendar';
         
         let tokenClient;
         let gapiInited = false;
         let gisInited = false;
         
         document.getElementById('authorize_button').style.visibility = 'hidden';
         document.getElementById('signout_button').style.visibility = 'hidden';
         document.getElementById('addto_cal').style.visibility = 'hidden';
         
         /**
          * Callback after api.js is loaded.
          */
         function gapiLoaded() {
           gapi.load('client', intializeGapiClient);
         }
         
         /**
          * Callback after the API client is loaded. Loads the
          * discovery doc to initialize the API.
          */
         async function intializeGapiClient() {
           await gapi.client.init({
             apiKey: API_KEY,
             discoveryDocs: [DISCOVERY_DOC],
           });
           gapiInited = true;
           // 页面加载时尝试恢复保存的token
           await restoreSavedToken();
           maybeEnableButtons();
         }
         
         /**
          * 恢复本地存储的token并验证有效性
          */
         async function restoreSavedToken() {
           const savedToken = localStorage.getItem('googleCalendarToken');
           if (!savedToken) return;
           
           const token = JSON.parse(savedToken);
           // 检查token是否过期
           if (Date.now() >= token.expires_at) {
             // token过期,尝试静默刷新
             tokenClient.requestAccessToken({prompt: ''});
             return;
           }
           
           // 恢复token到gapi客户端
           gapi.client.setToken(token);
           // 更新UI状态
           document.getElementById('signout_button').style.visibility = 'visible';
           document.getElementById('authorize_button').innerText = 'Refresh';
           document.getElementById('addto_cal').style.visibility = 'visible';
           await listUpcomingEvents();
         }
         
         /**
          * Callback after Google Identity Services are loaded.
          */
         function gisLoaded() {
           tokenClient = google.accounts.oauth2.initTokenClient({
             client_id: CLIENT_ID,
             scope: SCOPES,
             callback: '', // defined later
           });
           gisInited = true;
           maybeEnableButtons();
         }
         
         /**
          * Enables user interaction after all libraries are loaded.
          */
         function maybeEnableButtons() {
           if (gapiInited && gisInited) {
             document.getElementById('authorize_button').style.visibility = 'visible';
           }
         }
         
         /**
          *  Sign in the user upon button click.
          */
         function handleAuthClick() {
           tokenClient.callback = async (resp) => {
             if (resp.error !== undefined) {
               throw (resp);
             }
             // 将token存储到localStorage
             localStorage.setItem('googleCalendarToken', JSON.stringify(gapi.client.getToken()));
             document.getElementById('signout_button').style.visibility = 'visible';
             document.getElementById('authorize_button').innerText = 'Refresh';
             document.getElementById('addto_cal').style.visibility = 'visible';
             await listUpcomingEvents();
           };
         
           if (gapi.client.getToken() === null) {
             // Prompt the user to select a Google Account and ask for consent to share their data
             // when establishing a new session.
             tokenClient.requestAccessToken({prompt: 'consent'});
           } else {
             // Skip display of account chooser and consent dialog for an existing session.
             tokenClient.requestAccessToken({prompt: ''});
           }
         }
         
         /**
          *  Sign out the user upon button click.
          */
         function handleSignoutClick() {
           const token = gapi.client.getToken();
           if (token !== null) {
             google.accounts.oauth2.revoke(token.access_token);
             gapi.client.setToken('');
             // 清除本地存储的token
             localStorage.removeItem('googleCalendarToken');
             document.getElementById('content').innerText = '';
             document.getElementById('authorize_button').innerText = 'Authorize';
             document.getElementById('signout_button').style.visibility = 'hidden';
             document.getElementById('addto_cal').style.visibility = 'hidden';
           }
         }
         
         function addto_cal()
         {
           try {
             var resource = {
               "summary": "Vimal Bus Booking",
               "location": "Chennai",
                 'description': "Trichy to Chennai at 9:00 PM",
               "start": {
                 "dateTime": "2022-08-30T10:00:00.000-07:00"
               },
               "end": {
                 "dateTime": "2022-09-01T10:25:00.000-07:00"
                 }
               };
             var request = gapi.client.calendar.events.insert({
               'calendarId': 'primary',
               'resource': resource
             });
             request.execute(function(resp) {
               console.log(resp);
             });   
           } 
           
           catch (err) {
             document.getElementById('content').innerText = err.message;
             return;
           }
         }
         /**
          * Print the summary and start datetime/date of the next ten events in
          * the authorized user's calendar. If no events are found an
          * appropriate message is printed.
          */
         async function listUpcomingEvents() {
           let response;
           try {
             const request = {
               'calendarId': 'primary',
               'timeMin': (new Date()).toISOString(),
               'showDeleted': false,
               'singleEvents': true,
               'maxResults': 10,
               'orderBy': 'startTime',
             };
             response = await gapi.client.calendar.events.list(request);
           } catch (err) {
             document.getElementById('content').innerText = err.message;
             return;
           }
         
           const events = response.result.items;
           if (!events || events.length == 0) {
             document.getElementById('content').innerText = 'No events found.';
             return;
           }
           // Flatten to string to display
           const output = events.reduce(
               (str, event) => `${str}${event.summary} (${event.start.dateTime || event.start.date})\n`,
               'Events:\n');
           document.getElementById('content').innerText = output;
         }
      </script>
      <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
      <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
   </body>
</html>

功能说明

  • localStorage持久化存储token,避免页面刷新后丢失
  • restoreSavedToken函数自动检查本地token,未过期则直接恢复登录状态,过期则静默刷新token
  • 登录成功后立即保存token,登出时同步清除本地存储

内容的提问来源于stack exchange,提问作者Vimal M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 15:36:28