在Gmail Add-Ons中实现OAuth2隐式授权流的技术问询
我之前在做Gmail Add-On的时候刚好实现过OAuth2隐式授权流,结合你已经用到的StateTokenBuilder和/usercallback端点,给你梳理下完整的实现细节和关键代码,帮你打通整个流程:
一、核心流程梳理
你已经走通了第一步:用StateTokenBuilder生成安全的state参数,拼接出授权URL让用户跳转认证。接下来的关键是处理授权成功后的重定向——因为隐式授权的access_token是放在URL的hash部分,Apps Script的服务器端无法直接获取,得通过前端页面中转提取,再把token保存起来供后续使用。
二、关键代码实现
1. 生成带State的授权URL
确保把StateToken生成的参数拼到redirect_uri里,用来防止CSRF攻击:
function getAuthorizationUrl() { // 生成带回调方法的StateToken,有效期1小时 const stateToken = ScriptApp.newStateToken() .withMethod('handleOAuthCallback') .withTimeout(3600) .createToken(); // 拼接隐式授权的URL,注意response_type必须是token const authUrl = 'https://你的授权服务器域名/auth?' + 'client_id=你的客户端ID' + '&response_type=token' + '&redirect_uri=' + encodeURIComponent(`https://script.google.com/macros/d/你的脚本ID/usercallback?state=${stateToken}`) + '&scope=你需要的权限列表'; return authUrl; }
2. 处理授权回调
因为access_token在URL hash里,需要用HtmlService创建一个临时页面提取参数,再传给服务器端保存:
function handleOAuthCallback(request) { // 用前端JS提取hash中的授权参数 return HtmlService.createHtmlOutput(` <script> // 解析hash部分的参数 const hashParams = new URLSearchParams(window.location.hash.slice(1)); const accessToken = hashParams.get('access_token'); const tokenType = hashParams.get('token_type'); const expiresIn = hashParams.get('expires_in'); // 调用服务器端函数保存token,成功后关闭页面 google.script.run .withSuccessHandler(() => window.close()) .saveOAuthTokens(accessToken, tokenType, expiresIn); </script> `); } // 把token保存到用户专属的Properties中 function saveOAuthTokens(accessToken, tokenType, expiresIn) { const userProps = PropertiesService.getUserProperties(); userProps.setProperty('OAUTH_ACCESS_TOKEN', accessToken); userProps.setProperty('OAUTH_TOKEN_TYPE', tokenType); // 计算并保存过期时间戳,方便后续检查 const expireTime = Date.now() + parseInt(expiresIn) * 1000; userProps.setProperty('OAUTH_EXPIRE_TIME', expireTime.toString()); }
3. 在Gmail Add-On中触发授权
在卡片里添加授权按钮,判断token状态(未授权/过期则显示按钮,已授权则展示业务内容):
function buildMainCard() { const userProps = PropertiesService.getUserProperties(); const accessToken = userProps.getProperty('OAUTH_ACCESS_TOKEN'); const expireTime = userProps.getProperty('OAUTH_EXPIRE_TIME'); let card; // 检查token是否存在且未过期 if (!accessToken || Date.now() > parseInt(expireTime)) { const authUrl = getAuthorizationUrl(); card = CardService.newCardBuilder() .setHeader(CardService.newCardHeader().setTitle('需要授权')) .addSection(CardService.newCardSection() .addWidget(CardService.newTextButton() .setText('点击完成授权') .setOpenLink(CardService.newOpenLink() .setUrl(authUrl) .setOpenAs(CardService.OpenAs.OVERLAY) // 用弹窗打开,体验更好 .setOnClose(CardService.OnClose.RELOAD_ADD_ON)))) // 关闭后自动刷新插件 .build(); } else { // 已授权状态,展示你的业务逻辑内容 card = CardService.newCardBuilder() .setHeader(CardService.newCardHeader().setTitle('授权成功')) .addSection(CardService.newCardSection() .addWidget(CardService.newTextParagraph().setText('已获取有效访问令牌,可以开始使用功能啦'))) .build(); } return [card]; }
三、重要注意事项
- 必须在你的OAuth应用后台,把
https://script.google.com/macros/d/你的脚本ID/usercallback添加到允许的重定向URI列表里,否则授权服务器会拒绝跳转。 - StateToken是必须的,它能有效防止CSRF攻击,Apps Script的/usercallback端点会验证这个参数的合法性,不要省略。
- 隐式授权不会返回
refresh_token,所以要提前监控token过期时间,过期后引导用户重新授权。 - 用
UserProperties保存token,每个用户的token是独立存储的,不会互相干扰。
内容的提问来源于stack exchange,提问作者Aakash Kumar
相关产品推荐
相关产品推荐

