DocuSign Node SDK JWT认证调用Envelope接口返回444错误排查
最终更新
问题已参照官方发布的修复方案解决,根源与DocuSign API新增的安全机制有关。
初始问题描述
同一段业务代码此前运行完全正常,上周客户演示时所有功能均可正常调用,再次运行时接口返回444错误,错误返回内容如下:
{ "status": 444, "response": { "req": { "method": "GET", "url": "https://demo.docusign.net/restapi...", "data": {}, "headers": { "user-agent": "node-superagent/3.8.2", "x-docusign-sdk": "Node", "authorization": "Bearer ABC...", "content-type": "application/json", "accept": "application/json" } }, "header": { "content-type": "text/html", "x-docusign-tracetoken": "2eeb8caa-8865-4898-bef9-d3611bfaa3f7", "x-docusign-node": "DA2DFE5", "date": "Fri, 17 Jun 2022 01:02:02 GMT", "content-length": "54", "connection": "close", "strict-transport-security": "max-age=31536000; includeSubDomains" }, "status": 444, "text": "The custom error module does not recognize this error." } }
调用链路为:通过Node SDK调用requestJWTUserToken方法获取JWT令牌,给ApiClient设置认证请求头后初始化EnvelopesApi实例,调用getEnvelope()或listStatusChanges()方法时固定返回上述错误。相关代码已经数月未做改动,集成密钥、账号、私钥等所有配置与此前完全一致,之前多次演示、测试均无问题。
排查过程中发现核心异常点:错误响应里携带的请求URL、JWT令牌均有效,把令牌复制到Postman中直接调用对应envelopes接口可以正常返回结果,只有SDK发起的请求会失败。
- 最初使用docusign-esign 5.15版本,升级到5.17版本后问题仍然存在
- 该问题直接导致此前可正常运行的演示系统不可用,初期未定位到根因
更新1:抓包排查结果
通过本地代理转发Node应用经DocuSign Node SDK发出的所有请求,抓包查看失败请求的实际内容:
(附失败请求抓包截图)
所有SDK发起的业务请求均返回相同错误:
HTTP/1.1 444 Content-Type: text/html X-DocuSign-TraceToken: 338534c6-c8c3-4b01-9b66-35d697cd0053 X-DocuSign-Node: DA1DFE4 Date: Fri, 17 Jun 2022 03:55:07 GMT Content-Length: 54 Vary: Accept-Encoding Connection: close Strict-Transport-Security: max-age=31536000; includeSubDomains The custom error module does not recognize this error.
用抓包工具把失败的请求直接导出为cURL命令,在终端执行该命令可以正常请求成功:
[MacBookPro0020]~/source/docusign/jwt-smoke-test:0 (master) $ curl 'https://demo.docusign.net/restapi/v2.1/accounts/a0a4c81f-.../envelopes?envelope_ids=e750526f-...&envelope_ids=a38b794b...&envelope_ids=a5d8c586-...' \ -H 'Host: demo.docusign.net' \ -H 'User-Agent: node-superagent/3.8.2' \ -H 'X-DocuSign-SDK: Node' \ -H 'Node-Ver: v14.18.3' \ -H 'Authorization: Bearer ABCD...' \ -H 'Accept: application/json' \ -H 'Connection: close' \ -H 'Content-Type: application/json' \ --proxy http://localhost:9090 {"resultSetSize":"1","startPosition":"0","endPosition":"0","totalSetSize":"1","nextUri":"","previousUri":"","envelopes":[{"status":"created","documentsUri":"/envelopes/d97565c8...purgeState":"unpurged","envelopeIdStamping":"true","autoNavigation":"true","isSignatureProviderEnvelope":"false","allowComments":"true","anySigner":null,"envelopeLocation":"current_site"}]}
经确认JWT令牌完全有效,只有SDK发起的调用持续失败,cURL、Postman手动发起的相同参数请求全部可以正常返回,此时仍未定位到问题原因。
补充排查到的信息:
- MacOS、Windows环境下运行集成docusign-esign的Node应用都会复现该问题
- 使用授权码模式的相关功能(发送信封、查询信封状态)可正常运行
- 出问题当周之前JWT授权模式一直可以正常使用,上周还演示过相关自动化流程
- 核对代码仓库提交记录,团队成员均未改动过DocuSign相关的业务代码
此前从未遇到过同类错误,相同参数的手动请求可以成功,暂时不考虑放弃SDK自行封装请求逻辑。
更新2:最小复现示例
基于官方Node SDK QuickStart项目编写最小复现代码,仅依赖docusign-esign模块,可以100%复现上述问题。多次验证确认:代码中获取到的令牌放到cURL、Postman中请求完全正常,代码逻辑无特殊处理,令牌本身有效。
async function main() { // 依赖配置项 // dsConfig.dsClientId 集成密钥 // dsConfig.impersonatedUserGuid impersonation用户ID // dsConfig.privateKey RSA私钥 // dsConfig.dsOauthServer OAuth服务地址 let dsConfig = dsConfig_customer; // 全局定义的配置对象 const jwtLifeSec = 10 * 60, // 申请的JWT有效期为10分钟 dsApi = new docusign.ApiClient(); dsApi.setOAuthBasePath(dsConfig.dsOauthServer.replace('https://', '')); // 此处仅需传入域名 const results = await dsApi.requestJWTUserToken(dsConfig.dsClientId, dsConfig.impersonatedUserGuid, 'signature impersonation', dsConfig.privateKey, jwtLifeSec); console.log( results.body.access_token ); const userInfo = await dsApi.getUserInfo(results.body.access_token); dsApi.setBasePath(userInfo.accounts[0].baseUri + '/restapi'); dsApi.addDefaultHeader( 'Authorization', 'Bearer ' + results.body.access_token ); const envelopesAPI = new docusign.EnvelopesApi(dsApi); const res = await envelopesAPI.getEnvelope( dsConfig.accountID, 'e1917111-2900-48e8-9054-799169379c8a', null ); console.log(res); return { accessToken: results.body.access_token, tokenExpirationTimestamp: expiresAt, userInfo, account: userInfo.accounts[0] }; } main().then(result => console.log(result)).catch(err=>console.error(err));
运行上述代码返回的错误片段如下:
... header: { 'content-type': 'text/html', 'x-docusign-tracetoken': '685b6226-a0d3-4547-94c7-df0216d884a3', 'x-docusign-node': 'DA2DFE188', date: 'Fri, 17 Jun 2022 05:20:12 GMT', 'content-length': '54', vary: 'Accept-Encoding', connection: 'close', 'strict-transport-security': 'max-age=31536000; includeSubDomains' }, statusCode: 444, status: 444, statusType: 4, info: false, ok: false, redirect: false, clientError: true, serverError: false, error: Error: cannot GET /restapi/v2.1/accounts/49754554-ABCD-.../envelopes/e1917111-2900-48e8-9054-799169379c8a (444) ...
内容的提问来源于stack exchange,提问作者user14375540

