【问题标题】:Error connecting to azure blob storage API from R从 R 连接到 Azure Blob 存储 API 时出错
【发布时间】:2015-05-29 20:06:24
【问题描述】:

我正在尝试通过 R 中的 the REST API 使用 Azure 存储。我正在使用覆盖 Curl 的包 httr

设置

您可以使用 R-fiddle:http://www.r-fiddle.org/#/fiddle?id=vh8uqGmM

library(httr)
requestdate<-format(Sys.time(),"%a, %d %b %Y %H:%M:%S GMT")
url<-"https://preconstuff.blob.core.windows.net/pings?restype=container&comp=list"
sak<-"Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ=="
signaturestring<-paste0("GET",paste(rep("\n",12),collapse=""),
"x-ms-date:",requestdate,"
x-ms-version:2009-09-19
/preconstuff/pings
comp:list
restype:container")

headerstuff<-add_headers(Authorization=paste0("SharedKey preconstuff:",
                         RCurl::base64(digest::hmac(key=sak,
                         object=enc2utf8(signaturestring),
                         algo= "sha256"))),
                    `x-ms-date`=requestdate,
                    `x-ms-version`= "2009-09-19")

尝试列出 blob:

content(GET(url,config = headerstuff, verbose() ))

错误

顶级消息

在 HTTP 请求“Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ==”中找到的 MAC 签名与任何计算的签名不同。

回复内容

[1] "<?xml version=\"1.0\" encoding=\"utf-8\"?><Error>
<Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:1ab26da5-0001-00dc-6ddb-15e35c000000\nTime:2015-03-26T17:51:42.7190620Z</Message>
<AuthenticationErrorDetail>The MAC signature found in the HTTP request 'NTM1ODZjMjhhZmMyZGM3NDM0YTFjZDgwNGE0ODVmMzVjNDhkNjBkNzk1ZjNkZjJjOTNlNjUxYTMwMjRhNzNlYw==' is not the same as any computed signature. Server used following string to sign: 
'GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Thu, 26 Mar 2015 17:52:37 GMT\nx-ms-version:2009-09-19\n/preconstuff/pings\ncomp:list\nrestype:container'.
</AuthenticationErrorDetail></Error>"

详细输出

-> GET /pings?restype=container&comp=list HTTP/1.1
-> User-Agent: curl/7.39.0 Rcurl/1.95.4.5 httr/0.6.1
-> Host: preconstuff.blob.core.windows.net
-> Accept-Encoding: gzip
-> Accept: application/json, text/xml, application/xml, */*
-> Authorization: SharedKey preconstuff:OTRhNTgzYmY3OTY3M2UzNjk3ODdjMzk3OWM3ZmU0OTA4MWU5NTE2OGYyZGU3YzRjNjQ1M2NkNzY0ZTcyZDRhYQ==
-> x-ms-date: Thu, 26 Mar 2015 17:56:27 GMT
-> x-ms-version: 2009-09-19
-> 
<- HTTP/1.1 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
<- Content-Length: 719
<- Content-Type: application/xml
<- Server: Microsoft-HTTPAPI/2.0
<- x-ms-request-id: 3d47770c-0001-0085-2313-6d466f000000
<- Date: Thu, 26 Mar 2015 17:56:27 GMT
<- 

解决错误

谷歌搜索这个问题似乎并没有产生一致的原因,但这可能是由于我的格式/请求结构不正确。为此,我检查了:

  1. 我已验证我的密钥是正确的(它只是来自门户的 c&p)
  2. 我已确保日期格式正确
  3. 有一个recent documentDB SO 表明它可能是时钟偏差问题,我确实注意到我的 x-ms-date 比响应中的 Date 早一秒。我试过发送一个过去肯定存在的固定值,但在 15 分钟的容差范围内。未收到消息更改。
  4. headerstuff 中添加了encoding="Base64"MSDN forum question,但返回了相同的错误消息
  5. 在@Serdar 的回答的基础上,我合并了签名字符串的构造(我已经验证这与错误消息中提供的字符串匹配),然后在 base64 中编码一个 hmac-sha256(使用辅助访问密钥(sak) 作为加密密钥)UTF8 版本转换为 signaturestring 作为共享密钥授权中使用的值。
  6. 除@Serdar 的评论外,签名字符串中使用的日期和主请求中使用的日期必须相同,因此定义一次并重复使用

有什么明显的错误吗?还有其他需要检查的吗?该代码对其他人有用吗?

【问题讨论】:

    标签: r azure-storage httr


    【解决方案1】:

    您似乎直接在 Authorization 标头中使用了您的帐户密钥。要对请求进行身份验证,您必须使用发出请求的帐户的密钥对请求进行签名,并将该签名作为请求的一部分传递。有关如何构造 Authorization 标头的更多信息,请参阅Authentication for the Azure Storage Services

    还请注意,服务在错误响应中返回 StringToSign。因此,您的代码应该做的是将以下公式应用于 StringToSign="GET\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Wed, 2015 年 3 月 25 日 22:24:12 GMT\nx-ms-version:2014-02-14\n/preconstuff/pings\ncomp:list\nrestype:container"(不带引号):

    Signature=Base64(HMAC-SHA256(AccountKey, UTF8(StringToSign)))
    

    服务如何计算 StringToSign 在上面共享的链接中有详细说明。

    【讨论】:

    • 为快速响应干杯,我仍在阅读 BOL 的那一部分,但没有得到它。看着关于构建签名字符串的那一点,我很困惑(我是一个 API 的相对新手)并且没有 VERB 反对的 URL。您是否能够粗略地了解一下您如何看待它在通话方面的工作方式?我可能会从那里跑过来为你完善你的答案。
    • 我刚刚在答案中添加了更多信息。我不知道 R,所以我没有示例代码,但你的 VERB 基本上是 GET。请尝试将文档与作为 StringToSign 的服务返回的内容进行比较。然后您应该使用您的帐户密钥签署该字符串。
    • 认为 我正在按照说明进行操作,但仍然无法正常工作:-/ 问题已更新为包含 StringToSign 的最新代码
    • 1) 您正在调用 Sys.time() 两次。您添加到 StringToSign 的日期和标头必须完全匹配,因为 StringToSign 应该包含这些标头的确切值。 2) 为什么要在 StringToSign 中添加超时,即使您的 URL 中没有超时?
    【解决方案2】:

    看来您的问题出在密钥上。您提供的密钥字符串实际上是 base64 编码的。在使用原始向量对请求进行签名之前,您需要将其解码为原始向量。例如:

    url<-"https://preconstuff.blob.core.windows.net/pings?restype=container&comp=list"
    sak<-"Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ=="
    
    requestdate<-format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
    signaturestring<-paste0("GET",paste(rep("\n",12),collapse=""),
    "x-ms-date:",requestdate,"
    x-ms-version:2009-09-19
    /preconstuff/pings
    comp:list
    restype:container")
    
    headerstuff<-add_headers(Authorization=paste0("SharedKey preconstuff:",
                             RCurl::base64(digest::hmac(key=RCurl::base64Decode(sak, mode="raw"),
                             object=enc2utf8(signaturestring),
                             algo= "sha256", raw=TRUE))),
                        `x-ms-date`=requestdate,
                        `x-ms-version`= "2009-09-19")
    
    content(GET(url,config = headerstuff, verbose() ))
    

    虽然没有列出任何 blob,但这种方式不会再出现身份验证错误。也许这是一个不同的问题。

    此外,我更改了创建日期/时间的方式,以便更“安全”地将本地时间更改为 GMT。

    【讨论】:

    • 你@MrFlick 是个天才!干杯。没有斑点,因为我首先在 PUT 之前尝试 GET
    • 它也是连接到 DocumentDB 集合的有效解决方案(带有一些不同的标题)。发送@MrFlick !!
    【解决方案3】:

    通过上面 MrFlick 的示例代码工作,为了让它工作,我不得不改变一些事情。

    日期字符串必须设置为英语语言环境,例如:

    lct <- Sys.getlocale("LC_TIME") 
    Sys.setlocale("LC_TIME", "us")
    requestdate <- format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
    Sys.setlocale("LC_TIME", lct)
    

    'signaturestring' 应该在参数之间用 \n 格式化:

     signaturestring <- paste0("GET", paste(rep("\n", 12), collapse=""),
    "x-ms-date:", requestdate, 
    "\nx-ms-version:2009-09-19\n/preconstuff/pings\ncomp:list\nrestype:container")
    

    编辑:以下程序对我有用。基于斯蒂芬洛克的例子。

    library(httr)
    library(RCurl)
    
    azureBlobCall <- function(url, verb, key, requestBody=NULL, headers=NULL, ifMatch="", md5="") { 
      urlcomponents <- httr::parse_url(url)
      account <- gsub(".blob.core.windows.net", "", urlcomponents$hostname, fixed = TRUE)
      container <- urlcomponents$path
    
      # get timestamp in us locale
      lct <- Sys.getlocale("LC_TIME"); Sys.setlocale("LC_TIME", "us")
      `x-ms-date` <- format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
      Sys.setlocale("LC_TIME", lct)
    
      # if requestBody exist get content length in bytes and content type
      `Content-Length` <- ""; `Content-Type` <- ""
      if(!is.null(requestBody)) {
        if(class(requestBody) == "form_file") {
          `Content-Length` <- (file.info(requestBody$path))$size
          `Content-Type` <- requestBody$type 
        } else {
          requestBody <- enc2utf8(as.character(requestBody))
          `Content-Length` <- nchar(requestBody, "bytes")
          `Content-Type` <- "text/plain; charset=UTF-8" 
        }
      } 
    
      # combine timestamp and version headers with any input headers, order and create the CanonicalizedHeaders
      headers <- setNames(c(`x-ms-date`, "2015-04-05",  unlist(headers)), 
                          c("x-ms-date", "x-ms-version", unclass(names(unlist(headers)))))
      headers <- headers[order(names(headers))]
      CanonicalizedHeaders <- paste(names(headers), headers, sep=":", collapse = "\n")
    
      # create CanonicalizedResource headers and add any queries to it
      if(!is.null(urlcomponents$query)) {
        components <- setNames(unlist(urlcomponents$query), unclass(names(unlist(urlcomponents$query))))
        componentstring <- paste0("\n", paste(names(components[order(names(components))]),
                                              components[order(names(components))], sep=":", collapse = "\n"))
      } else componentstring <- ""
      CanonicalizedResource <- paste0("/",account,"/",container, componentstring)
    
      # create the authorizationtoken
      signaturestring <- paste0(verb, "\n\n\n", `Content-Length`, "\n", md5, "\n", `Content-Type`, "\n\n\n", 
                                ifMatch, "\n\n\n\n", CanonicalizedHeaders, "\n", CanonicalizedResource)
    
      requestspecificencodedkey <- RCurl::base64(
        digest::hmac(key=RCurl::base64Decode(key, mode="raw"),
                     object=enc2utf8(signaturestring),
                     algo= "sha256", raw=TRUE)
      )
    
      authorizationtoken <- paste0("SharedKey ", account, ":", requestspecificencodedkey)
    
      # make the call
      headers_final <- add_headers(Authorization=authorizationtoken, headers, `Content-Type` = `Content-Type`)
      call <- httr::VERB(verb=verb, url=url, config=headers_final, body=requestBody, verbose())
    
      print("signaturestring");print(signaturestring); 
      print(headers_final); print(call)
      return(content(call))
    } 
    
    ## Tests. Replace 'key' and 'accountName' with yours
    key <- "YowThr***********RDw=="
    
    # Creates a container named 'test'
    azureBlobCall("https://accountName.blob.core.windows.net/test?restype=container", "PUT", key)
    # Creates a blob named 'blob' under container 'test' with the content of "Hej världen!"
    azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "PUT", key, 
                  headers = c("x-ms-blob-type"="BlockBlob"), requestBody = "Hej världen!") #upload_file("blob.txt"))
    # List all blob in the container 'test'
    azureBlobCall("https://accountName.blob.core.windows.net/test?comp=list&restype=container", "GET", key)
    # deletes the blobl named 'blob' 
    azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "DELETE", key)
    # Creates a blob named 'blob' under container 'test' with and upload the file 'blob.txt'
    azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "PUT", key, 
                  headers = c("x-ms-blob-type"="BlockBlob"), requestBody = upload_file("blob.txt"))
    # deletes the container named 'test' 
    azureBlobCall("https://accountName.blob.core.windows.net/test?restype=container", "DELETE", key)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-08-03
      • 1970-01-01
      • 2016-10-13
      • 2019-06-12
      • 1970-01-01
      • 2021-07-11
      • 2017-09-17
      • 1970-01-01
      相关资源
      最近更新 更多