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

如何安全实现外部服务器调用非公开Google Apps Script Web App

安全调用非公开Google Apps Script Web App的解决方案

方案一:基于服务账号的OAuth2客户端凭证验证

利用你已有的服务账号,通过client_credentials类型的OAuth2令牌实现无交互验证,同时限制Web App访问范围。

1. 关联Apps Script项目与目标GCP项目

  • 打开Apps Script项目,进入项目设置,勾选「显示appsscript.json清单文件」
  • 编辑appsscript.json,添加必要的OAuth权限范围(根据业务需求调整):
    {
      "oauthScopes": [
        "https://www.googleapis.com/auth/script.external_request",
        "https://www.googleapis.com/auth/drive.readonly" // 需调用Drive API时添加
      ],
      "runtimeVersion": "V8"
    }
    
  • 返回项目设置,找到「Google Cloud Platform (GCP) 项目」,点击「更改项目」,选择服务账号所在的GCP项目

2. 部署受限制的Web App

  • 点击「部署」→「部署为Web应用」:
    • 执行权限:选择「以部署者身份执行」(确保服务账号能触发Web App业务逻辑)
    • 访问权限:选择「仅限特定用户」并添加服务账号邮箱(格式:xxx@xxx.iam.gserviceaccount.com),或选择「我的组织内的任何人」(仅适用于Google Workspace账号)
    • 点击部署,记录Web App访问URL

3. Web App端添加令牌验证逻辑

在doPost(或doGet)函数中,验证传入的Bearer令牌是否来自合法服务账号:

function doPost(e) {
  // 提取Authorization头中的令牌
  const authHeader = e.headers.Authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return ContentService.createTextOutput('Unauthorized').setStatusCode(401);
  }
  const token = authHeader.split(' ')[1];

  // 校验令牌合法性
  try {
    const response = UrlFetchApp.fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${token}`);
    const tokenInfo = JSON.parse(response.getContentText());
    
    // 验证令牌所属服务账号
    const allowedServiceAccount = 'your-service-account@your-project.iam.gserviceaccount.com';
    if (tokenInfo.email !== allowedServiceAccount) {
      return ContentService.createTextOutput('Forbidden').setStatusCode(403);
    }
  } catch (err) {
    return ContentService.createTextOutput('Invalid token').setStatusCode(401);
  }

  // 执行业务逻辑
  return ContentService.createTextOutput('Success').setStatusCode(200);
}

4. C#端实现令牌获取与调用

复用现有服务账号密钥,获取令牌并调用Web App:

using Google.Apis.Auth.OAuth2;
using System.Net.Http;
using System.Threading.Tasks;

public async Task InvokeWebApp()
{
    // 加载服务账号密钥文件
    var credential = GoogleCredential.FromFile("service-account-key.json")
        .CreateScoped(new[] { "https://www.googleapis.com/auth/script.external_request" });
    
    // 获取访问令牌
    var accessToken = await credential.UnderlyingCredential.GetAccessTokenForRequestAsync();
    
    // 发起请求
    using var httpClient = new HttpClient();
    httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
    
    var response = await httpClient.PostAsync("https://script.google.com/macros/s/your-web-app-id/exec", null);
    response.EnsureSuccessStatusCode();
    
    var result = await response.Content.ReadAsStringAsync();
    // 处理返回结果
}

方案二:无OAuth2的双重验证(IP白名单+API密钥)

若不想使用OAuth2,可通过IP限制+自定义API密钥实现安全访问,适合固定IP的服务器场景。

1. Web App端添加验证逻辑

function doPost(e) {
  // 1. 验证请求来源IP(替换为公司服务器IP/IP段)
  const allowedIps = ["192.168.1.100", "203.0.113.0/24"];
  const clientIp = e.headers["X-Forwarded-For"]?.split(",")[0] || e.remoteAddress;
  
  const isIpAllowed = allowedIps.some(ip => {
    if (ip.includes("/")) {
      // 简化IP段验证,可按需完善
      const [baseIp, mask] = ip.split("/");
      return clientIp.startsWith(baseIp.split(".").slice(0, mask/8).join("."));
    }
    return clientIp === ip;
  });
  
  if (!isIpAllowed) {
    return ContentService.createTextOutput("Forbidden: Invalid IP").setStatusCode(403);
  }

  // 2. 验证自定义API密钥
  const validApiKey = "your-secret-random-api-key"; // 自行生成并妥善保管
  const incomingApiKey = e.headers["X-API-Key"] || e.parameter.apiKey;
  
  if (incomingApiKey !== validApiKey) {
    return ContentService.createTextOutput("Unauthorized: Invalid API Key").setStatusCode(401);
  }

  // 执行业务逻辑
  return ContentService.createTextOutput("Success").setStatusCode(200);
}

2. C#端调用实现

在请求头中携带API密钥:

using System.Net.Http;
using System.Threading.Tasks;

public async Task InvokeWebApp()
{
    using var httpClient = new HttpClient();
    // 添加API密钥到请求头
    httpClient.DefaultRequestHeaders.Add("X-API-Key", "your-secret-random-api-key");
    
    var response = await httpClient.PostAsync("https://script.google.com/macros/s/your-web-app-id/exec", null);
    response.EnsureSuccessStatusCode();
    
    var result = await response.Content.ReadAsStringAsync();
    // 处理返回结果
}

关键注意事项

  • 方案一中,需在GCP项目中启用Apps Script API
  • 服务账号需被添加到Web App的允许访问列表中
  • 方案二中,API密钥需通过环境变量或配置文件管理,禁止硬编码
  • 若公司服务器使用动态IP,需改用静态IP或调整IP白名单策略

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:37:08