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

Moodle LTI基础成果服务请求随机失败:签名无效排查

问题:LTI Basic Outcome Service 成绩回传随机签名验证失败

我正在开发一款作为Moodle外部工具的测验游戏,遵循IMS LTI规范并使用OAuth进行认证。目前已完成Moodle发起的POST启动请求的认证,正尝试通过LTI Basic Outcome Service将成绩从我的工具回传至Moodle。

在此过程中遇到问题:我构建POX消息、签名并发送请求,但请求仅约30%的概率成功,其余请求会收到Moodle返回的失败POX消息,描述为Message signature not valid。


成功请求示例

  1. 请求Base String:
POST&http%3A%2F%2F127.0.0.1%2Fmoodle%2Fmod%2Flti%2Fservice.php &oauth_body_hash%3DhPssgohenJEvtKta2so7Y27p3kU%253D%26oauth_callback%3Dabout%253Ablank %26oauth_consumer_key%3Dkey%26oauth_nonce%3D63cc0764c4cc4701abe28fa5fd406378 %26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1525940779%26oauth_version%3D1.0
  1. 响应体:
<?xml version="1.0" encoding="UTF-8"?> 
<imsx_POXEnvelopeResponse xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"> 
<imsx_POXHeader> 
<imsx_POXResponseHeaderInfo> 
<imsx_version>V1.0</imsx_version> 
<imsx_messageIdentifier>1602471533</imsx_messageIdentifier> 
<imsx_statusInfo> 
<imsx_codeMajor>success</imsx_codeMajor> 
<imsx_severity>status</imsx_severity> 
<imsx_description>Result read</imsx_description> 
<imsx_messageRefIdentifier>1339905165</imsx_messageRefIdentifier> 
<imsx_operationRefIdentifier>readResultRequest</imsx_operationRefIdentifier> 
</imsx_statusInfo> 
</imsx_POXResponseHeaderInfo> 
</imsx_POXHeader> 
<imsx_POXBody> 
<readResultResponse> 
<result> 
<resultScore> 
<language>en</language> 
<textString>0.5</textString> 
</resultScore> 
</result> 
</readResultResponse> 
</imsx_POXBody> 
</imsx_POXEnvelopeResponse>

失败请求示例

  1. 请求Base String:
POST&http%3A%2F%2F127.0.0.1%2Fmoodle%2Fmod%2Flti%2Fservice.php &oauth_body_hash%3DYLigJE%252B8wr7rCwOITqdc1IP3zFs%253D%26oauth_callback%3Dabout%253Ablank %26oauth_consumer_key%3Dkey%26oauth_nonce%3D6de4380ce2ab4d9a90e3fe1723dc5141 %26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1525940816%26oauth_version%3D1.0
  1. 响应体:
<?xml version="1.0" encoding="UTF-8"?> 
<imsx_POXEnvelopeResponse xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"> 
<imsx_POXHeader> 
<imsx_POXResponseHeaderInfo> 
<imsx_version>V1.0</imsx_version> 
<imsx_messageIdentifier>1688463600</imsx_messageIdentifier> 
<imsx_statusInfo> 
<imsx_codeMajor>failure</imsx_codeMajor> 
<imsx_severity>status</imsx_severity> 
<imsx_description>Message signature not valid</imsx_description> 
<imsx_messageRefIdentifier/> 
<imsx_operationRefIdentifier>unknownRequest</imsx_operationRefIdentifier> 
</imsx_statusInfo> 
</imsx_POXResponseHeaderInfo> 
</imsx_POXHeader> 
<imsx_POXBody> 
<unknownResponse/> 
</imsx_POXBody> 
</imsx_POXEnvelopeResponse>

可见两个Base String仅timestamp、nonce等应变化的部分不同,无法定位请求随机失败的原因,恳请提供可能的原因及排查建议。


补充签名流程代码

private IEnumerator SendGradeRequest(XmlDocument xml) { 
    string url = parametrosIniciales["lis_outcome_service_url"]; 
    byte[] entityBody = Encoding.UTF8.GetBytes(xml.OuterXml); 
    string bodyHash = oAuth.GetBodyHash(entityBody); 
    Dictionary<string, string> oAuthParameters = oAuth.PrepareOAuthParameters(oAuth.GetSessionOAuthParameters(parametrosIniciales)); 
    oAuthParameters.Add("oauth_body_hash", bodyHash); 
    Dictionary<string, string> headers = new Dictionary<string, string>(); 
    headers.Add("Content-Type", "application/xml"); 
    headers.Add("Authorization", oAuth.GetAuthori

可能的原因及排查建议

从你的描述和示例来看,最可能的问题出在签名生成的细节上,以下是具体的排查方向:

  • Base String中的URL空格问题:
    观察你提供的成功/失败Base String,发现service.php后面都带有一个空格,然后才是&连接参数串。而实际请求的URL是没有这个空格的,这会导致Moodle端计算的Base String与你生成的不一致——Moodle用的是正确的无空格URL,而你的带空格,自然签名不匹配。这个空格可能是字符串拼接时的疏忽,比如代码中拼接URL时不小心多了一个空格,而这个问题可能随机出现(比如某些情况下URL字符串末尾有空格,某些情况下没有),这直接解释了随机失败的现象。

  • 请求体与oauth_body_hash的一致性:
    oauth_body_hash是请求体的SHA1哈希(Base64编码),必须和实际发送的XML完全一致。排查点:

    • 检查xml.OuterXml是否会随机生成不同的格式(比如自动添加缩进、换行,或者XML声明的差异),导致字节数组变化;
    • 确认编码是UTF-8无BOM,如果生成的entityBody带BOM,会导致哈希计算错误;
    • 发送请求时,有没有对请求体做额外修改(比如框架自动添加了某些字符)。
  • OAuth参数的排序与编码:
    OAuth签名要求所有参数(包括oauth_*和oauth_body_hash)按字典序排序,且每个键值对严格遵循RFC 3986编码。比如:

    • 检查PrepareOAuthParameters方法是否正确排序了所有参数;
    • 确认编码时所有特殊字符(如+、%、空格)都被正确转义,比如%要转成%25,空格转%20。
  • 时间同步与Nonce唯一性:

    • Moodle通常会拒绝时间戳与本地时间差超过5分钟的请求,检查你的服务器与Moodle服务器的时间是否同步,有没有偶尔出现时间跳变;
    • 确保每次请求生成的Nonce是全局唯一的,重复的Nonce哪怕搭配不同的时间戳,也可能触发验证失败。
  • Authorization头格式:
    检查生成的Authorization头是否严格符合OAuth规范,比如参数之间用逗号分隔,每个参数值用双引号包裹(如oauth_consumer_key="key"),格式错误会导致Moodle无法正确解析签名参数,从而返回签名无效。

  • HMAC-SHA1密钥正确性:
    LTI签名的密钥格式是consumer_secret&(注意末尾的&,如果没有token secret的话),确认你的代码中没有偶尔错误使用了其他密钥。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:30:41