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

使用Google Apps Script中UrlfetchApp调用Absorb LMS API遇403错误

问题描述

自2023年6月起,使用Google Apps Script的UrlfetchApp调用Absorb LMS API(地址:https://rest.myabsorb.eu/)时持续收到403 Forbidden响应,此前代码已正常运行数月。尝试V1和V1.5版本API结果一致,脚本调用其他API正常,但Postman调用Absorb API完全正常,且其他用户也遇到同类问题。

原代码如下:

function getAbsorbToken() { 
  if(absorbToken==null) {
    var sFunction = "Authenticate";
    var sPayload = JSON.stringify({"Username": absorbUsername,"Password": absorbPass,"PrivateKey": absorbPrivateKey});
    var result = callAbsorbAPI(sFunction,"POST",sPayload);
    if(result[0]) { 
      var goodToken = result[1].toString(); 
      goodToken = goodToken.substring(1,goodToken.length-1); 
      absorbToken = goodToken; 
      return true;
    } else { Logger.log("ERROR getAborsbToken"); return false; }
  } else {return true;}
}

function callAbsorbAPI(sFunction, sMethod, sPayload='',sHeaders = "",sGetRequest = "") {
  var url = absorbURL+"api/Rest/v1/"+sFunction;
  if(sGetRequest!="") url += "/"+sGetRequest;
  var headers = {
    'method': sMethod,
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'muteHttpExceptions': true
  };
  if(sPayload != '') {
    headers['payload']=sPayload;
  }
  if(sHeaders != '') {
    headers['headers']=sHeaders;
  }
  var response = UrlFetchApp.fetch(url, headers);
  var responseCode = response.getResponseCode();
  if(responseCode==200 || responseCode == 201) {
    return [true,response];
  } else {
    Logger.log(response+" "+responseCode);
    return [false,response];
  }
}
问题分析与修复方案

1. 请求头结构错误(核心问题)

Google Apps Script的UrlFetchApp.fetch选项参数中,HTTP请求头必须放在headers子对象内,而非顶级属性。原代码直接将Content-Type、Accept放在选项对象的顶级,导致这些头未被正确发送,这是触发403的关键原因之一。

2. 缺失User-Agent头

Postman会自动添加标准的User-Agent,而UrlFetchApp默认的User-Agent可能被Absorb API的反爬机制拦截,需要手动添加符合规范的User-Agent。

3. Token处理逻辑不严谨

原代码通过toString()和字符串截取获取Token的方式不可靠,应直接解析响应JSON内容。

修复后的代码

// 确保全局变量已正确定义:absorbToken, absorbUsername, absorbPass, absorbPrivateKey, absorbURL
function getAbsorbToken() { 
  if (!absorbToken) {
    const sFunction = "Authenticate";
    const sPayload = JSON.stringify({
      "Username": absorbUsername,
      "Password": absorbPass,
      "PrivateKey": absorbPrivateKey
    });
    const result = callAbsorbAPI(sFunction, "POST", sPayload);
    if (result[0]) {
      const responseData = JSON.parse(result[1].getContentText());
      absorbToken = responseData; // 根据Absorb API实际返回结构调整,若返回带引号字符串则取对应字段
      return true;
    } else {
      Logger.log("ERROR getAbsorbToken");
      return false;
    }
  } else {
    return true;
  }
}

function callAbsorbAPI(sFunction, sMethod, sPayload='', sHeaders = {}, sGetRequest = "") {
  let url = `${absorbURL}api/Rest/v1/${sFunction}`;
  if (sGetRequest) {
    url += `/${sGetRequest}`;
  }
  
  // 构建正确的请求选项结构
  const options = {
    method: sMethod,
    muteHttpExceptions: true,
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
      ...sHeaders // 合并自定义请求头
    }
  };
  
  if (sPayload) {
    options.payload = sPayload;
  }
  
  const response = UrlFetchApp.fetch(url, options);
  const responseCode = response.getResponseCode();
  
  if (responseCode === 200 || responseCode === 201) {
    return [true, response];
  } else {
    Logger.log(`响应错误:${response.getContentText()} 状态码:${responseCode}`);
    return [false, response];
  }
}

额外排查点

  • 确认Absorb API是否更新了IP白名单规则,可通过在脚本中调用https://api.ipify.org获取当前出口IP,检查是否需加入白名单。
  • 验证API密钥、用户名密码是否过期或权限变更。
  • 若调用V1.5 API,需调整URL路径为api/Rest/v1.5/${sFunction}。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 19:13:35