Moodle LTI基础成果服务请求随机失败:签名无效排查
我正在开发一款作为Moodle外部工具的测验游戏,遵循IMS LTI规范并使用OAuth进行认证。目前已完成Moodle发起的POST启动请求的认证,正尝试通过LTI Basic Outcome Service将成绩从我的工具回传至Moodle。
在此过程中遇到问题:我构建POX消息、签名并发送请求,但请求仅约30%的概率成功,其余请求会收到Moodle返回的失败POX消息,描述为Message signature not valid。
成功请求示例
- 请求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
- 响应体:
<?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>
失败请求示例
- 请求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
- 响应体:
<?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

