QuickBooks Online使用过滤器查询每次都返回401错误

8
我已经成功使用POST和Content-Type为application/xml创建对象。同时,我也成功地使用Content-Type为application/x-www-form-urlencoded的空请求体进行查询,根据我指定的URI返回所有对象类型。我还可以在请求体中使用类似于PageNum=1&ResultsPerPage=1的内容,并将其合并到签名中,以获得有效的响应。
然而,无论如何格式化它,当我尝试使用筛选器(例如Filter=FAMILYNAME:EQUALS:Doe)时,我都只能得到401响应。我已经阅读了OAuth Core 1.0修订A规范,其中所有参数名称和值都使用[RFC3986]百分号编码进行转义。但是我觉得我可能漏掉了一个步骤或者格式化有误。在Intuit的论坛上搜索时,我看到了不一致的信息,不确定正确的格式到底是什么。
如果您能给予任何帮助,我将不胜感激。我已经苦苦挣扎了一个星期。
当尝试使用筛选器时,我收到的响应是: HTTP状态401 - message = Exception authenticating OAuth; errorCode = 003200; statusCode = 401
----更新----
当我尝试使用新的IPP开发人员工具 - IPP API Explorer筛选器时,我看到了相同的错误。 我正在使用IDS V2 QBO API Explorer工具。 我可以使用该工具检索所有帖子,响应显示了我的所有客户,但是当我尝试使用筛选器时,我会收到以下错误: 服务器错误 401-未经授权:由于凭据无效而拒绝访问。 您没有权限使用您提供的凭据查看此目录或页面。
有什么想法吗? 如果我从API Explorer工具中获得相同的错误,那么问题可能完全不同。
----最后更新----
我最终成功地使用了筛选器,并且我相信我已经找出了我的问题所在。 我一直怀疑能够像“PageNum = 1&ResultsPerPage = 1”这样进行分页查询,但无法获得类似于“Filter = FAMILYNAME:EQUALS:Doe”的内容。 我怀疑问题出在筛选器格式中的空格。 让我困惑的是,我无法使IDS V2 QBO API Explorer中的筛选器正常工作。 这让我怀疑还有其他问题。 我决定完全忽略API Explorer并集中精力解决为什么可以以一种方式使其工作,但不是另一种方式。
我相信我的问题归结为筛选器值在签名中的错误编码。 这就解释了我得到的401无效签名错误。 "Filter = Name:EQUALS:Doe"在规范化后变为"Filter = Name%20%3AEQUALS%20%3ADoe"。
百分号编码应该为“Filter%3DName%2520%253AEQUALS%2520%253ADoe”。

本质上,您需要对空格和冒号进行“双重”编码,但不要对等号进行编码。我尝试了许多编码组合,但认为我的错误在于我要么没有“双重”编码,要么当我双重编码时,包括了“=”符号。无论哪种方式都会破坏您的签名。感谢大家的建议。


听起来很傻,不过你试过用%20替换空格了吗? - armani
我正在使用一个函数来进行RFC3986编码,但我也尝试了手动编码和其他几种编码方式,但都没有成功。感谢您的建议。 - JoshASI
恭喜你成功创建了自己的OAuth签名...我也曾经历过这样的事情,让人抓狂。 - Andy Jones
3个回答

2
我相信我的问题归结于签名中过滤器值的不正确编码。这解释了我遇到的401无效签名错误。
我使用了一个在线工具来指导我正确签署Oauth请求的步骤。在执行这些步骤时,我意识到我的问题出在规范化请求参数并对其进行百分比编码的步骤上。我在规范化步骤中包含了过滤器的'=',这会破坏你的签名。我使用的工具可以在以下网址找到:

http://hueniverse.com/2008/10/beginners-guide-to-oauth-part-iv-signing-requests/

谢谢大家的参与。

很棒的工具 - 使用它,我能够发现我对QBOv3 API运行的查询没有被正确编码,因此导致我的OAuth签名无效,并在每个请求上产生相同的message=Exception authenticating OAuth; errorCode=003200; statusCode=401问题。 - JaredC

1

我正在使用静态的基本URL。我会尝试切换到运行时的基本URL并发布我的结果。谢谢。 - JoshASI
我仍在考虑使用静态基本URL。但是我担心在IPP Developer Tool API Explorer中无法使过滤器正常工作。在IDS V2 QBO API Explorer中,通过将请求正文留空,我可以检索所有客户。但是,如果我尝试输入类似“Filter=FAMILYNAME :EQUALS: Doe”的过滤器,我会收到401-未经授权的错误:由于凭据无效而拒绝访问。当我尝试通过我的软件的POST调用使用过滤器时,我看到的行为是相同的。我开始怀疑这里还有其他问题。有人有任何想法吗? - JoshASI
我们正在研究这个问题,稍后会告知您。 - William Lorfing
我尝试切换到运行时基本URL,但是使用过滤器没有成功。我仍然遇到了一个问题,即请求正文中的PageNum=1&ResultsPerPage=1有效,但过滤器无法工作。我正在使用Fiddler来组合我的请求和响应的完整示例,以便将其发送给Intuit的联系人。希望他们能够提供帮助。 - JoshASI
目前还没有任何人回应为什么 IDS V2 QBO API Explorer 中的过滤器无法工作。我也注意到,我无法使用分页功能使 API Explorer 工作。在 API Explorer 中,当进行“检索全部”操作时,如果您在请求正文中输入任何内容,它就不起作用(401 错误)。如果将请求正文留空,则可以正常工作,并且我可以获取所有客户或其他对象的列表。对我来说似乎有点问题。 - JoshASI
显示剩余2条评论

1

peterl在这里回答了我的一个问题,也许可以回答你的问题。我一直试图将过滤器放在正文中,而它们应该放在标题中。以下是peterl的代码示例,用于获取特定客户的所有未付发票(未结余额大于0.00)。

http://pastebin.com/raw.php?i=7VUB6whp

public List<Intuit.Ipp.Data.Qbo.Invoice> GetQboUnpaidInvoices(DataServices dataServices, int startPage, int resultsPerPage,  IdType CustomerId)
{
    StringBuilder requestXML = new StringBuilder();
    StringBuilder responseXML = new StringBuilder();

    var requestBody = String.Format("PageNum={0}&ResultsPerPage={1}&Filter=OpenBalance :GreaterThan: 0.00 :AND: CustomerId :EQUALS: {2}", startPage, resultsPerPage, CustomerId.Value);

    HttpWebRequest httpWebRequest = WebRequest.Create(dataServices.ServiceContext.BaseUrl + "invoices/v2/" + dataServices.ServiceContext.RealmId) as HttpWebRequest;
    httpWebRequest.Method = "POST";
    httpWebRequest.ContentType = "application/x-www-form-urlencoded";
    httpWebRequest.Headers.Add("Authorization", GetDevDefinedOAuthHeader(httpWebRequest, requestBody));
    requestXML.Append(requestBody);
    UTF8Encoding encoding = new UTF8Encoding();
    byte[] content = encoding.GetBytes(requestXML.ToString());
    using (var stream = httpWebRequest.GetRequestStream())
    {
        stream.Write(content, 0, content.Length);
    }
    HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
    using (Stream data = httpWebResponse.GetResponseStream())
    {
        Intuit.Ipp.Data.Qbo.SearchResults searchResults = (Intuit.Ipp.Data.Qbo.SearchResults)dataServices.ServiceContext.Serializer.Deserialize<Intuit.Ipp.Data.Qbo.SearchResults>(new StreamReader(data).ReadToEnd());
        return ((Intuit.Ipp.Data.Qbo.Invoices)searchResults.CdmCollections).Invoice.ToList();
    }

}

protected string GetDevDefinedOAuthHeader(HttpWebRequest webRequest, string requestBody)
{

    OAuthConsumerContext consumerContext = new OAuthConsumerContext
    {
        ConsumerKey = consumerKey,
        ConsumerSecret = consumerSecret,
        SignatureMethod = SignatureMethod.HmacSha1,
        UseHeaderForOAuthParameters = true

    };

    consumerContext.UseHeaderForOAuthParameters = true;

    //URIs not used - we already have Oauth tokens
    OAuthSession oSession = new OAuthSession(consumerContext, "https://www.example.com",
                            "https://www.example.com",
                            "https://www.example.com");


    oSession.AccessToken = new TokenBase
    {
        Token = accessToken,
        ConsumerKey = consumerKey,
        TokenSecret = accessTokenSecret
    };

    IConsumerRequest consumerRequest = oSession.Request();
    consumerRequest = ConsumerRequestExtensions.ForMethod(consumerRequest, webRequest.Method);
    consumerRequest = ConsumerRequestExtensions.ForUri(consumerRequest, webRequest.RequestUri);
    if (webRequest.Headers.Count > 0)
    {
        ConsumerRequestExtensions.AlterContext(consumerRequest, context => context.Headers = webRequest.Headers);
        if (webRequest.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded")
        {
            Dictionary<string, string> formParameters = new Dictionary<string, string>();
            foreach (string formParameter in requestBody.Split('&'))
            {
                formParameters.Add(formParameter.Split('=')[0], formParameter.Split('=')[1]);
            }
            consumerRequest = consumerRequest.WithFormParameters(formParameters);
        }
    }

    consumerRequest = consumerRequest.SignWithToken();
    return consumerRequest.Context.GenerateOAuthParametersForHeader();
}

您还可以在StackOverflow上查看我的原始问题:使用QuickBooks Online(QBO)Intuit合作伙伴平台(IPP)DevKit查询所有未结余额的发票


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接