【问题标题】:Delphi XE2 - always getting 401 Unauthorized () trying to get OAuth Access Token from IntuitDelphi XE2 - 总是得到 401 Unauthorized () 试图从 Intuit 获取 OAuth 访问令牌
【发布时间】:2014-02-12 19:57:29
【问题描述】:

我正在与 Intuit 一起完成 OAuth 舞蹈的最后一步。

这是一个 Delphi XE2 桌面应用程序,我正在使用 Indy 组件。

我已成功从 Intuit 获取我的请求令牌,并且在我授权我的应用程序后,来自他们网站的回调正确地将我重定向到我们的 Web 服务。

使用从 Intuit 返回的验证器,我构建了获取访问令牌的请求。

当我调用 Intuit 时,我总是收到 HTTP/1.1 401 Unauthorized 错误。如果我将构建的 URL 发布到 Chrome 浏览器中,我会收到“oauth_problem=signature_invalid”消息。

显然,我没有完全掌握 OAuth 的东西,但我非常确定我的签名是正确的。我在这里使用谷歌的签名检查工具:http://oauth.googlecode.com/svn/code/javascript/example/signature.html,我的签名总是匹配的。我的签名基础也匹配。

以下是我用来生成签名的键/值以及我从结果中生成的 URL:

http method: GET
URL: https://oauth.intuit.com/oauth/v1/get_access_token
singing method: HMAC-SHA1
version: 1.0
ConsumerKey: qyprdGItCTwCAbvx3qNSJR5vDnCOVX
ConsumerSecret: 7IxtFa7aYU2O0PvMGSKsEqaZRvQFoZFaPCtNqg2D
RequestToken: qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt
TokenSecret: AWYbBHrhYhUTYlD1kdPRal2NXRwWMo8qDZ7HDBTQ
TimeStamp: 1392232483
nonce: 3EE3F797E97810F1F0CDF866BEB1AA68

如果我将上述所有内容插入 Google 的签名工具,我会得到与我从代码中获得的相同签名。我的签名库也与我从 Google 工具中获得的匹配。

Signature Base: GET&https%3A%2F%2Foauth.intuit.com%2Foauth%2Fv1%2Fget_access_token&oauth_consumer_key%3DqyprdGItCTwCAbvx3qNSJR5vDnCOVX%26oauth_nonce%3D3EE3F797E97810F1F0CDF866BEB1AA68%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1392232483%26oauth_token%3DqyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt%26oauth_version%3D1.0
signature : F7m3ii6is226/waoA5wkhrFN/Ak=
signature URL encoded: F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D

将我从下面的代码构建的 URL 粘贴到 Chrome 浏览器中会出现 oauth_problem 错误。

URL:  https://oauth.intuit.com/oauth/v1/get_access_token?oauth_consumer_key=qyprdGItCTwCAbvx3qNSJR5vDnCOVX&oauth_token=qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt&oauth_signature_method=HMAC-SHA1&oauth_signature=F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D&oauth_timestamp=1392232483&oauth_nonce=3EE3F797E97810F1F0CDF866BEB1AA68&oauth_version=1.0&oauth_verifier=dv9xx1q

请记住,在为 get_request_token 构建 URL 时,生成签名的代码可以正常工作。这只是获取最重要的访问令牌和访问令牌秘密的最后一点,这对我来说一切都崩溃了。

我在 oauth.net 文档 (http://oauth.net/core/1.0a/#auth_step3) 中读到,最后一次调用应该通过 HTTP POST 完成,但我没有看到任何描述帖子正文应该包含的内容,并且 Intuit 合作伙伴平台上也没有任何内容描述 POST 正文所以我正在尝试 GET 。我尝试过一个空的 POST 也没有运气。

感谢任何建议。

干杯! TJ

【问题讨论】:

  • TJ...只是好奇:你有没有想过这个问题?我的下一个项目可能是通过 IPP 接口连接到 QB Online 的桌面,所以 OAuth 是一个亟待解决的问题......
  • @Mark - 很遗憾,没有。我确信 Intuit 使用的代码的实现有点奇怪,它并没有像我期望的那样进行签名。他们正在通过网站代码将请求令牌转换为访问令牌。我们做了一些诡计,允许我们将秘密存储在我们的网络服务上,并使用与我们的网络服务器上的 Intuit 相同的网站代码 dll 来进行从请求令牌/秘密到访问令牌/秘密的转换。它有效但不漂亮。如果您需要详细信息,请 PM 或给我发电子邮件。

标签: delphi oauth delphi-xe2 intuit-partner-platform


【解决方案1】:

当您在浏览器中输入 URL 并放开它时,它会执行 GET 请求。但就像您提到的,以及您链接到的 Intuit documentation 所说的,get_access_token 请求应该使用 POST 请求来完成。

如果您阅读 Intuit 文档的 section 5.2,它会说:

OAuth 协议参数以三种方法之一从消费者发送到服务提供者,按优先级降序排列:

  1. 在 OAuth HTTP 授权方案中定义的 HTTP 授权标头中。
  2. 作为 HTTP POST 请求正文,内容类型为 application/x-www-form-urlencoded。
  3. 添加到查询部分的 URL(由 RFC3986 第 3 节定义)。

当你应该做 #2 时,你正在做 #3。正确的 POST 请求看起来像这样:

POST /oauth/v1/get_access_token HTTP/1.1
Host: oauth.intuit.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 307

oauth_consumer_key=qyprdGItCTwCAbvx3qNSJR5vDnCOVX&oauth_token=qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt&oauth_signature_method=HMAC-SHA1&oauth_signature=F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D&oauth_timestamp=1392232483&oauth_nonce=3EE3F797E97810F1F0CDF866BEB1AA68&oauth_version=1.0&oauth_verifier=dv9xx1q

您提到您正在使用 Indy,因此可以使用如下代码发送这样的 POST 请求:

var
  Params: TStringList;
begin
  Params := TStringList.Create;
  try
    Params.Values['oauth_consumer_key'] := 'qyprdGItCTwCAbvx3qNSJR5vDnCOVX';
    Params.Values['oauth_token'] := 'qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt';
    Params.Values['oauth_signature_method'] := 'HMAC-SHA1';
    Params.Values['oauth_signature'] := 'F7m3ii6is226/waoA5wkhrFN/Ak='; // <-- pay attention here!
    Params.Values['oauth_timestamp'] := '1392232483';
    Params.Values['oauth_nonce'] := '3EE3F797E97810F1F0CDF866BEB1AA68';
    Params.Values['oauth_version'] := '1.0';
    Params.Values['oauth_verifier'] := 'dv9xx1q';

    IdHTTP.Post('https://oauth.intuit.com/oauth/v1/get_access_token', Params);
  finally
    Params.Free;
  end;
  ...
end;

特别注意oauth_signature 值。发送到服务器的内容必须经过 url 编码:

F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D

默认情况下,TIdHTTP在准备POST请求时会为你对TStringList数据进行url编码,所以不要将url编码的数据放在TStringList中:

F7m3ii6is226/waoA5wkhrFN/Ak=

但是,如果要将 url 编码的数据放入 TStringList,则必须禁用 TIdHTTP.HTTPOptions 属性中的 hoForceEncodeParams 标志。 TIdHTTP 仍将TStringList 数据作为name-value 对发送,每对之间带有&amp; 字符,但每对的名称和值将按原样发送,而不会由TIdHTTP 进行url 编码。

【讨论】:

  • Remy,非常感谢您简洁明了的回复!但是,我仍然使用您上面概述的发布方法从 Intuit 收到 401 Unauthorized 错误。我向我们服务器上的非 https url 发送了一个帖子,因此我可以看到内容通过 Wireshark 传输,它看起来很像您的示例。 “Accept”和“Accept-Encoding”和“User-Agent”还有其他参数。我仔细检查以确保签名没有被双重 ULR 编码。有什么建议吗?
  • 我还注意到我的帖子是 HTTP/1.0,而不是 HTTP/1.1,即使我将 ProtocolVersion 设置为 pv1_1。我不确定这是否重要,但我现在正在考虑任何事情。
  • 好的,在 Indy 代码中看到 HTTP/1.0 总是强制用于 POST。附加信息:我在 INDY 代码中挖掘了 Intuit 回复的标题,并看到相同的签名无效回复:WWW-Authenticate: OAuth oauth_problem="signature_invalid" FULL HEADERS: 'Date: Thu, 13 Feb 2014 15:16 :31 GMT'#$D#$A'Server: Apache'#$D#$A'WWW-Authenticate: OAuth oauth_problem="signature_invalid"'#$D#$A'Cache-Control: no-cache, no-store'# $D#$A'Pragma: no-cache'#$D#$A'Content-Length: 31'#$D#$A'Connection: close'#$D#$A'Content-Type: text/plain'#$D# $A'X-Pad:避免浏览器错误'#$D#$A
  • 您可以在HTTPOptions 属性中启用hoKeepOrigProtocol 标志来发送HTTP 1.1 请求。您也可以尝试发送“在 OAuth HTTP 授权方案中定义的 HTTP 授权标头中”的数据。 (在 Intuit 的首选传输方案列表中排名第一,WWW-Authenticate: OAuth 标头的存在也表明了这一点)。您可以为此使用TIdHTTP.Request.CustomHeaders.Values['Authorization'] := ...;。如果它仍然不起作用,那么您可能必须联系 Intuit 并让他们调试您发送给他们的数据。
猜你喜欢
  • 1970-01-01
  • 2017-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多