Google Docs插件登录后重定向问题:OAuth2集成报错排查
在Google Docs插件中集成OAuth2的重定向问题
我想在Google Docs插件中集成OAuth2,当前通过插件打开新窗口进行授权,但重定向回插件时遇到问题。我使用项目设置中的https://script.google.com/macros/s/Script_ID/exec作为redirectUri,重定向时出现以下错误:
Google Docs遇到错误。请尝试重新加载此页面,或稍后再访问。
如需了解更多Google Docs编辑器相关信息,请访问我们的帮助中心。
对于带来的不便,我们深表歉意。
- Google Docs团队

请问如何正确重定向到我的插件?以下是我的实现示例:
code.gs文件函数
function getAuthorizationUrl() { var a = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; // Your app's Client ID from Azure Portal var clientId = "client_id"; // The correct callback URL after user authorization var redirectUri = "https://script.google.com/macros/s/Script_Id/exec"; // Scopes you are requesting access to (Microsoft Graph API scopes) var scope = "user.read mail.send openid profile email offline_access"; // State and nonce for security var state = "12345"; var nonce = "678910"; // Assemble the full authorization URL var authUrl = a + "?client_id=" + clientId + "&response_type=code" + // Requesting an authorization code "&redirect_uri=" + encodeURIComponent(redirectUri) + // The new redirect URL "&scope=" + encodeURIComponent(scope) + "&response_mode=fragment" + "&state=" + state + "&nonce=" + nonce; return authUrl; } // Function to handle the callback and exchange the authorization code for an access token function doGet(e) { var code = e.parameter.code; // Get the authorization code from the query parameters if (code) { // Proceed to exchange the authorization code for an access token // You can call a function to exchange the code for tokens and continue the flow return HtmlService.createHtmlOutput('Authorization complete! You can close this window.'); } else { return HtmlService.createHtmlOutput('Error: Authorization failed or no code returned.'); } }
HTML文件代码
<button onclick="login()">Sign in with OAuth</button> <script> function login() { // Get the OAuth URL from the server google.script.run.withSuccessHandler(function(authUrl) { // Open the OAuth dialog in a new window window.open(authUrl, '_blank', 'width=600,height=600'); }).getAuthorizationUrl(); } </script>
问题分析与解决方案
核心问题
你遇到的错误根源是**response_mode=fragment的使用**:该模式会将授权码放在URL的片段(#后)中,而Google Apps Script的doGet函数无法读取URL片段参数,导致无法获取code,进而触发Docs页面错误。
修复步骤
修改
response_mode为query
在getAuthorizationUrl函数中,将&response_mode=fragment替换为&response_mode=query,这样授权码会通过URL查询参数传递,doGet可以正常获取code值。验证重定向URI配置
- 确保Google Apps Script项目已部署为Web应用,部署权限至少设置为“任何人,甚至匿名”(测试阶段);
- 在Azure Portal的应用注册中,重定向URI必须与代码中的
redirectUri完全一致(包括大小写、末尾的/exec)。
安全与逻辑优化
- 动态生成
state和nonce(不要用固定值),并在回调时验证state,防止CSRF攻击; - 授权完成后,让回调页面自动关闭并通知插件主窗口更新状态。
- 动态生成
修改后的完整代码示例
code.gs
function getAuthorizationUrl() { var authEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; // Azure Portal获取的客户端ID var clientId = "你的客户端ID"; // Web应用部署后的回调URL var redirectUri = "https://script.google.com/macros/s/你的脚本ID/exec"; // 请求的Microsoft Graph权限范围 var scope = "user.read mail.send openid profile email offline_access"; // 动态生成安全参数 var state = Utilities.getUuid(); var nonce = Utilities.getUuid(); // 组装授权URL,使用response_mode=query var authUrl = authEndpoint + "?client_id=" + clientId + "&response_type=code" + "&redirect_uri=" + encodeURIComponent(redirectUri) + "&scope=" + encodeURIComponent(scope) + "&response_mode=query" + "&state=" + state + "&nonce=" + nonce; // 存储state用于后续验证 PropertiesService.getUserProperties().setProperty("OAUTH_STATE", state); return authUrl; } function doGet(e) { var code = e.parameter.code; var state = e.parameter.state; var storedState = PropertiesService.getUserProperties().getProperty("OAUTH_STATE"); // 验证state合法性 if (!state || state !== storedState) { return HtmlService.createHtmlOutput('错误:非法请求,state验证失败。'); } if (code) { var token = exchangeCodeForToken(code); if (token) { // 保存令牌到用户属性 PropertiesService.getUserProperties().setProperty("ACCESS_TOKEN", token.access_token); PropertiesService.getUserProperties().setProperty("REFRESH_TOKEN", token.refresh_token); // 自动关闭窗口并通知插件授权成功 return HtmlService.createHtmlOutput('<script>window.close(); window.opener.google.script.run.notifyAuthSuccess();</script>'); } else { return HtmlService.createHtmlOutput('错误:换取访问令牌失败。'); } } else { return HtmlService.createHtmlOutput('错误:授权失败或未返回授权码。'); } } function exchangeCodeForToken(code) { var tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; var clientId = "你的客户端ID"; var clientSecret = "你的客户端密钥"; // 机密客户端需填写,公开客户端可省略 var redirectUri = "https://script.google.com/macros/s/你的脚本ID/exec"; var payload = { client_id: clientId, client_secret: clientSecret, code: code, redirect_uri: redirectUri, grant_type: "authorization_code" }; var options = { method: "post", payload: payload, muteHttpExceptions: true }; var response = UrlFetchApp.fetch(tokenEndpoint, options); var json = JSON.parse(response.getContentText()); return json.access_token ? json : null; } // 通知插件授权成功 function notifyAuthSuccess() { // 可在此更新插件UI,比如显示已登录状态 console.log("授权成功"); }
HTML文件
<button onclick="login()">使用OAuth登录</button> <div id="status"></div> <script> function login() { document.getElementById('status').textContent = "正在跳转授权..."; google.script.run.withSuccessHandler(function(authUrl) { window.open(authUrl, '_blank', 'width=600,height=600'); }).getAuthorizationUrl(); } // 接收授权成功通知 function notifyAuthSuccess() { document.getElementById('status').textContent = "授权成功!"; } </script>
内容的提问来源于stack exchange,提问作者M Јǚйáĩď
相关产品推荐
相关产品推荐

