使用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
相关产品推荐
相关产品推荐

