尝试使用 Oauth 1.0 获取 Twitter request_token 不断给出“215 Bad Authentication”错误

Attempt to get Twitter request_token using Oauth 1.0 keeps giving '215 Bad Authentication' error

我正在尝试从 Twitter 获取 Oauth 请求令牌 - 我正在按照他们的指南(下面的链接)进行操作,并且我已经检查了每个步骤大约 10 次,但我无法弄清楚为什么我得到此错误:

{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}

他们使用 Oauth 1.0。我应该在我的请求中组合所有 percent-encoded 参数,对它们进行百分比编码,添加请求方法 (POST) 和请求 url,使用它加上我的 api 密钥来创建签名,并将签名添加到我最终的 Post 请求中。签名文档在 this page and the subsequent Post request docs are on this page 上。我使用指南中给出的示例代码来生成签名,我得到了与指南相同的结果,所以我认为实际的签名生成没有问题。

我三次检查了我的 api 密钥和 api 密钥和回调 url。

这是我的完整代码。有人能看出问题吗?

require 'net/http'
require 'uri'
require 'json'
require 'base64'
require 'cgi'
require 'openssl'

oauth_callback = "http://www.example.localhost:3000/twittercallback"
oauth_consumer_key = '[KEY]'
oauth_timestamp = Time.now.to_i
oauth_nonce = SecureRandom.hex(10) + oauth_timestamp.to_s
oauth_signature_method = "HMAC-SHA1"
oauth_version = "1.0"


one1 = CGI.escape "oauth_callback"
one2 = CGI.escape oauth_callback
two1 = CGI.escape "oauth_consumer_key"
two2 = CGI.escape oauth_consumer_key
three1 = CGI.escape "oauth_nonce"
three2 = CGI.escape oauth_nonce
four1 = CGI.escape "oauth_signature_method"
four2 = CGI.escape oauth_signature_method
five1 = CGI.escape "oauth_timestamp"
five2 = CGI.escape oauth_timestamp.to_s
six1 = CGI.escape "oauth_version"
six2 = CGI.escape oauth_version
string = "#{one1}=#{one2}&#{two1}=#{two2}&#{three1}=#{three2}&#{four1}=#{four2}&#{five1}=#{five2}&#{six1}=#{six2}"
encoded_string = CGI.escape string
url = "https://api.twitter.com/oauth/request_token"
encoded_url = CGI.escape url
encoded_string = "POST&" + encoded_url + "&" + encoded_string
signing_key = '[SECRET]'
encoded_signing_key = (CGI.escape signing_key) + "&"
digest = OpenSSL::Digest::Digest.new( 'sha1' )
hmac = OpenSSL::HMAC.digest( digest, encoded_signing_key, encoded_string)
signature = Base64.encode64( hmac ).chomp.gsub( /\n/, '' )

uri = URI.parse("https://api.twitter.com/oauth/request_token")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/x-www-form-urlencoded"
oauth = 'OAuth oauth_callback="' + (CGI.escape oauth_callback) + '", oauth_nonce="' + (oauth_nonce + 'dsd') + '", oauth_signature_method="HMAC-SHA1", oauth_timestamp="' + oauth_timestamp.to_s + '", oauth_consumer_key="`<KEY>", oauth_signature="' + (CGI.escape signature) + '", oauth_version="1.0"'
request['Authorization'] = oauth
request.body = ""
req_options = {
    use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
end

这是我的 request["Authorization"] header

"OAuth oauth_callback=\"http%3A%2F%2Fwww.example.localhost%3A3000%2Ftwittercallback\", oauth_nonce=\"e5686f142ca58a45af233fb66876671592457549dsd\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1592457549\", oauth_consumer_key=\"[KEY]\", oauth_signature=\"n9MNLO%2Ft%2FT6WX0Myu5JcTICXNAQ%3D\", oauth_version=\"1.0\""

这是来自 Twitter docs:

的示例 header

OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw", oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1300228849", oauth_consumer_key="OqEqJeafRSF11jBMStrZz", oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D", oauth_version="1.0"

您没有提供所需的 Authorization header,而是为每个参数提供单独的 header。您需要将以下形式的参数组合为字符串,其中 OAuth 首先:

OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw",oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1300228849",oauth_consumer_key="OqEqJeafRSF11jBMStrZz",oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D",oauth_version="1.0"

并在 Authorization header 中提供:

request['Authorization'] = <OAuth string>

您的代码中还有许多其他问题无法正常工作:

  • 参数转义错误
  • 正在转义签名密钥
  • dsd 添加到 Authorization 中的 nonce header
  • 授权中的数据顺序错误 header(需要按字典顺序排列,例如,对于大多数图书馆来说,是按字母顺序排列的)

因此,如果我们重新编写您的代码,结果如下:

require 'net/http'
require 'uri'
require 'base64'
require 'cgi'
require 'openssl'
require 'securerandom'

oauth_consumer_key = CGI::escape('D6O7BIWc6MTgl0A8UaRRt83In')
oauth_timestamp = CGI::escape(Time.now.to_i.to_s)
oauth_nonce = CGI::escape(SecureRandom.hex(10) + oauth_timestamp.to_s)
oauth_signature_method = CGI::escape("HMAC-SHA1")
oauth_version = CGI::escape("1.0")

one1 = CGI::escape("oauth_consumer_key")
one2 = oauth_consumer_key
two1 = CGI::escape("oauth_nonce")
two2 = oauth_nonce
three1 = CGI::escape("oauth_signature_method")
three2 = oauth_signature_method
four1 = CGI::escape("oauth_timestamp")
four2 = oauth_timestamp
five1 = CGI::escape("oauth_version")
five2 = oauth_version
string = "#{one1}=#{one2}&#{two1}=#{two2}&#{three1}=#{three2}&#{four1}=#{four2}&#{five1}=#{five2}"
encoded_string = CGI.escape string
url = "https://api.twitter.com/oauth/request_token"
encoded_url = CGI.escape url
encoded_string = "POST&" + encoded_url + "&" + encoded_string
signing_key = 'xJPBJ2OdV6nM8r9e6ZysbnHTrZYm4R7LaY9OafNNNY3BkT4Oym'
encoded_signing_key = signing_key + '&'
digest = OpenSSL::Digest.new( 'sha1' )
hmac = OpenSSL::HMAC.digest( digest, encoded_signing_key, encoded_string)
signature = Base64.encode64( hmac ).chomp.gsub( /\n/, '' )

uri = URI.parse("https://api.twitter.com/oauth/request_token")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/x-www-form-urlencoded"
oauth = 'OAuth ' + one1 + '="' + oauth_consumer_key + '", ' + two1 + '="' + oauth_nonce + '", ' + CGI::escape('oauth_signature') + '="' + (CGI.escape signature) + '", ' + three1 + '="HMAC-SHA1", ' + four1 + '="' + oauth_timestamp.to_s  + '", ' + five1 + '="1.0"'
puts oauth
request['Authorization'] = oauth
request.body = ""
req_options = {
    use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
end
puts response
puts response.body

我还想给你一个更好的方法来处理参数来生成 base_stringheader:

require 'net/http'
require 'uri'
require 'base64'
require 'cgi'
require 'openssl'
require 'securerandom'

oauth_consumer_key = '<KEY>'
oauth_consumer_secret = '<SECRET>'
oauth_consumer_secret += '&'
oauth_timestamp = Time.now.getutc.to_i.to_s
oauth_nonce = SecureRandom.hex(10) + oauth_timestamp
oauth_signature_method = "HMAC-SHA1"
oauth_version = "1.0"

url = "https://api.twitter.com/oauth/request_token"
uri = URI.parse(url)
params = {
  'oauth_consumer_key' => oauth_consumer_key,
  'oauth_nonce' => oauth_nonce,
  'oauth_signature_method' => oauth_signature_method,
  'oauth_timestamp' => oauth_timestamp.to_s,
  'oauth_version' => oauth_version
}

base_string = "POST&" + CGI::escape(url) + "&" + CGI::escape(params.sort.collect{ |k,v| "#{CGI::escape(k)}=#{CGI::escape(v)}" }.join('&'))
oauth_signature = Base64.encode64("#{OpenSSL::HMAC.digest('sha1', oauth_consumer_secret, base_string)}").chomp

params['oauth_signature'] = oauth_signature

uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

header = "OAuth " + params.sort.collect {|k,v| "#{CGI::escape(k)}=\"#{CGI::escape(v)}\""}.join(", ")
rsp, data = http.post(uri, nil, {'Authorization' => header })

puts rsp
puts rsp.body

另一个建议是使用现有的 OAuth gem 可用。当您使用这个众所周知的库时,您还可以获得 Twitter.

的支持
gem install oauth
require 'oauth'

oauth_consumer = OAuth::Consumer.new("<KEY>", "<SECRET>", :site => "https://api.twitter.com")

access_token = OAuth::AccessToken.new(oauth_consumer)

request_token = access_token.request(:post, "/oauth/request_token")
rsp = request_token.body
puts rsp