PAYATHON 2026

每次打开或刷新页面都要求 Google Sign-in,如何保持授权状态?

数据阿明

结论

这段代码无法在页面刷新后恢复 Token。刷新会重置页面的 JavaScript 状态;初始化完成后,代码又会调用 authorize(),再次进入 requestAccessToken() 流程。

设置 prompt: '' 也不能保证授权过程中不出现窗口。如果希望刷新后无需用户再次点击就自动读取日历,应采用后端授权码流程,由后端保存 refresh token,并通过应用自己的会话识别用户。纯前端如果继续使用当前 Token model,应让用户通过按钮触发授权,并处理 access token 过期。Google Token model 文档、Code model 文档

为什么刷新页面就会重新请求授权?

代码中的调用链是:

gapi 和 GIS 初始化完成
→ maybeAuthorize()
→ authorize()
→ gapi.client.getToken() === null
→ requestAccessToken({ prompt: '' })

gapi.client.getToken() 检查的是当前页面中 API 客户端持有的 Token。它不能直接判断用户是否仍登录 Google,也不能说明用户过去是否同意过日历访问权限。

这里有三个需要区分的状态:

  • Google 账号的登录会话。
  • 用户对应用及其 scope 的授权记录。
  • 当前页面用于调用 Calendar API 的 access token。

Google 可以保留此前的授权记录,但当前页面仍需要取得可用的 access token。Token model 不会自动把该 Token 持久保存到浏览器中,供刷新后恢复。Google Token model 文档

因此,不能仅凭现有代码认定用户的 Google 登录会话每次都丢失。重复出现的界面究竟是账号选择、登录还是权限确认,需要根据实际界面判断。

prompt: '' 为什么不是完全静默授权?

原代码中的注释:

// First try silent auth (no UI)

对这次调用的描述不准确:

tokenClient.requestAccessToken({ prompt: '' });

根据 API 参考,空字符串用于避免用户在已有授权时反复确认。它不保证 OAuth 窗口不打开,也不会保存 Token。prompt: 'none' 表示不显示认证或同意界面,但如果流程需要用户交互,请求可能失败。仅替换这个参数不能实现持久化。Google OAuth JavaScript API 参考

纯前端方案:由用户点击后请求 Token

如果页面只在用户主动查看日历时访问 Calendar API,可以保留 initTokenClient(),将自动授权改为按钮触发。Google 的 Token model 指南要求通过按钮点击等用户操作发起 Token 请求。Google Token model 文档

在原有脚本前加入按钮:

<button id="authorize_button" type="button" onclick="authorize()" disabled>
  读取 Google 日历
</button>

将原来的 maybeAuthorize() 和 authorize() 替换为:

function maybeAuthorize() {
  document.getElementById('authorize_button').disabled =
    !(gapiInited && gisInited);
}

function authorize() {
  if (!gapiInited || !gisInited) {
    return;
  }

  tokenClient.requestAccessToken({ prompt: '' });
}

其余初始化逻辑和成功回调可以保留:取得 Token 后,再调用 createAllContent()。

这个改动会停止页面加载时自动发起授权,但刷新后仍需点击按钮。它适用于按需读取日历的纯前端页面,不能让页面在刷新后自动恢复数据访问。

还应为 initTokenClient() 配置 error_callback,处理 popup_failed_to_open、popup_closed 等窗口错误;这些错误与成功回调中的 OAuth 错误分开处理。Google OAuth JavaScript API 参考

需要刷新后自动读取日历时怎么做?

采用授权码流程,并由后端维护 Google API 凭据:

  1. 前端通过 google.accounts.oauth2.initCodeClient() 发起授权。
  2. 后端接收并校验授权请求,用 authorization code 换取 access token 和 refresh token。
  3. 后端安全保存 refresh token,并将凭据关联到应用用户。
  4. 应用通过自己的登录会话识别刷新页面后的用户。
  5. 页面向后端请求日历数据,由后端使用有效的 access token 调用 Calendar API,必要时通过 refresh token 换取新的 access token。

GIS Code model 支持这种流程。如果直接构造 Google Web Server OAuth 请求,需要按文档申请 access_type=offline;不要把该参数直接添加到现有的 initTokenClient() 配置中。Google Code model 文档、Web Server OAuth 离线访问说明

refresh token 也可能过期、失效或被用户撤销。后端在无法刷新时需要引导用户重新授权,因此无法保证永久免交互。

能否直接保存 access token?

可以自行实现短期缓存:保存 access token 和到期时间,刷新后确认尚未过期,再调用:

gapi.client.setToken({ access_token: cachedAccessToken });

但这只能在剩余有效期内复用 Token,无法自动续期。gapi.client.getToken() !== null 也不代表 Token 仍然有效。应结合 Token 响应中的 expires_in 管理到期时间,并处理 API 返回的认证失败。Google OAuth JavaScript API 参考

浏览器存储中的 Token 可被同源 JavaScript 读取,需要考虑 XSS 导致凭据泄露的风险。对于需要长期访问日历的应用,优先采用后端保存 refresh token 的方案,避免将长期凭据放进前端。

备注:内容仅供参考。