方括号破坏OAuth签名生成-如何对它们进行编码



我正在使用Ruby为一个使用OAuth的站点生成OAuth签名。到目前为止,它一直运行良好,直到我尝试添加一些带有方括号的参数。现在我收到一个"签名不匹配"错误。

我的猜测是,当Oauth生成签名时,它处理方括号的方式与我生成签名时处理它们的方式不同。

这就是我生成OAuth-sig(Ruby(的方式:

oauth_params = {
"oauth_consumer_key" => options["key"], #oAuth Consumer Key
"oauth_nonce" => MiscUtilities.generate_nonce, #oAuth Nonce - just a random numnber
"oauth_signature_method" => "HMAC-SHA1", #oAuth Signature Method - don't need to change this
"oauth_timestamp" => Time.now.to_i.to_s, #oAuth Timestamp, standard seconds since epoch.
"oauth_version" => "1.0" #oAuth Version - don't need to change this
}
#add any other non-oauth params we've been given
params = oauth_params.merge(options["params"])
#sort and url encode the params
encoded_params = params.sort_by{|k,v| k.to_s}.collect { |k, v| CGI.escape("#{k.to_s}=#{v}") }.join('%26')
signature_base_string = "#{options["method"]}&#{CGI.escape(options["api_url"])}&#{encoded_params}"
signing_key = "#{options["secret"]}&" 
#generate the signature and add it to params
digest = OpenSSL::Digest::Digest.new('sha1')
hmac = OpenSSL::HMAC.digest(digest, signing_key, signature_base_string)
params["oauth_signature"] = Base64.encode64(hmac).chomp
params  

就像我说的,在我把方括号加到混合物中之前,这一直很好。

有没有人遇到过这个问题,或者能够看到我可能做错了什么?

您不应该在URL或整个键值对上使用CGI:Escape。它应该用于键,并单独用于值。然后,这两个将用等号连接,然后将这些对添加到由&'分隔的查询字符串中s.

说到这里,我看到你加入了"%26"(编码&(。这似乎不符合规范,除非你的基本值可能不需要编码?这相当于对值进行"编码",然后对整个字符串进行编码。也许这就是括号引起问题的原因——一旦将它们添加到值中,就需要对该值进行编码。

encoded_params = Map [params.map {|k, v| CGI.Escape(k), CGI.Escape(v)}]
.sort_by{|k,v| k.to_s}
.collect { |k, v| CGI.escape("#{k.to_s}=#{v}") }
.join('%26')

我不是一个喜欢红宝石的人——我只是从一个关于处理哈希的答案中举了一个例子,并在这里应用了它。请做一些需要做的事情来生成正确的ruby代码。我还保留了代码的其余部分;虽然可能令人困惑,但它似乎能胜任这项工作。

还有另一个潜在的问题——根据规范,完成的URI编码可能与正常的URI编码有点不同。空格是%20而不是+,除ALPHA、DIGIT、'_'、'-'、'.'外的所有空格,和"~"必须进行编码,而不能对这些值进行编码。那么这适用于CGI:Escape吗?我没有安装ruby,我对规范的快速检查使我得到了一个单行文档。

edit:上面的算法不适用于需要编码的东西,因为来自编码的东西的%也将被编码。它只需要稍微调整一下就可以对整个值进行编码,而不是与编码的"与"符号连接。

诀窍是必须将签名基字符串中的方括号转换两次:

  • [=>%5B=>%255B
  • ]=>%5D=>%255D

我最终做了这个,我认为它不是特别干净,但确实有效。

#New version following https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html
def oauth_signed_params(options)
#defaults
options["method"] ||= "POST"
oauth_params = {
"oauth_consumer_key" => options["key"], #oAuth Consumer Key
"oauth_nonce" => generate_nonce, #oAuth Nonce - just a random number
"oauth_signature_method" => "HMAC-SHA1", #oAuth Signature Method - don't need to change this
"oauth_timestamp" => Time.now.to_i.to_s, #oAuth Timestamp, standard seconds since epoch.
"oauth_version" => "1.0" #oAuth Version - don't need to change this
}
params = oauth_params.merge(options["params"])
encoded_params = params.sort_by{|k,v| oauth_percent_encode(k.to_s)}.collect{|k, v| "#{oauth_percent_encode(k.to_s)}=#{oauth_percent_encode(v.to_s)}" }.join('&')
signature_base_string = "#{options["method"]}&#{oauth_percent_encode(options["api_url"])}&#{oauth_percent_encode(encoded_params)}"
signing_key = "#{options["secret"]}&" 
#generate the signature and add it to params
digest = OpenSSL::Digest::Digest.new('sha1')
hmac = OpenSSL::HMAC.digest(digest, signing_key, signature_base_string)
params["oauth_signature"] = Base64.encode64(hmac).chomp
params     
end
#according to this twitter oauth doc, we actually only percent-encode a limited set of characters.  
#Others are left as is. The ones in the regex below are NOT encoded.
#https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature.html
def oauth_percent_encode(string)
string.gsub(/[^a-zA-Z0-9-._~]/){|match| match == " " ? "%20" : CGI.escape(match)}
end

最新更新