You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Clojure调用Azure Blob Rest API遇403:请求Date头错误

问题:Clojure调用Azure Blob REST API报403认证错误

收到的错误信息:

:status 403
:reason-phrase Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
The Date header in the request is incorrect.

用户代码:

(require '[clj-http.client :as http])
(require '[java-time :as jt])

(import javax.crypto.Mac
        javax.crypto.spec.SecretKeySpec
        org.apache.commons.codec.binary.Base64)

(defn- canonicalized-resource [account-name container-name blob-name]
  (str "/" account-name "/" container-name "/" blob-name))

(defn get-authorization-header
  "Returns the Authorization header for a Blob Storage request."
  [account-name account-key request-date request-method content-type canonicalized-resource version]
  (let [string-to-sign (str request-method "\n\n\n\n\n"
                            content-type "\n"
                            "\n\n\n\n\n\n"
                            "x-ms-date:" request-date "\n"
                            "x-ms-version:" version "\n"
                            canonicalized-resource)
        key-bytes (Base64/decodeBase64 account-key)
        key-spec (SecretKeySpec. key-bytes "HmacSHA256")
        mac (Mac/getInstance "HmacSHA256")
        _ (.init mac key-spec)
        signature-bytes (.doFinal mac (.getBytes string-to-sign))
        signature (Base64/encodeBase64String signature-bytes)
        auth-header (str "SharedKey " account-name ":" signature)]
    auth-header))

(defn put-blob [account-name account-key container-name blob-name content-type file-path]
  (let [request-date (jt/format (jt/formatter :rfc-1123-date-time) (jt/zoned-date-time (jt/zone-offset 0)))
        version "2022-11-02"
        request-url (str "https://" account-name ".blob.core.windows.net/" container-name "/" blob-name)
        canon-resource (canonicalized-resource account-name container-name blob-name)
        auth-header (get-authorization-header account-name account-key request-date "PUT" content-type canon-resource version)
        file-content (slurp file-path)
        response (http/put request-url
                           {:body         (Base64/encodeBase64 (.getBytes file-content))
                            :content-type content-type
                            :headers      {"Content-Type"   content-type
                                           "x-ms-date"      request-date
                                           "x-ms-version"   version
                                           "x-ms-blob-type" "BlockBlob"
                                           "Authorization"  auth-header}
                            :debug        true})]
    (if (= (:status response) 201)
      (:body response)
      (throw (Exception. (:body response))))))

问题排查与修复方案

1. 日期格式不符合Azure要求

Azure要求x-ms-date必须是带GMT时区标识的RFC 1123格式,原代码生成的日期可能用+00:00替代GMT,触发日期校验失败。

修改日期生成逻辑:

(request-date (jt/format (jt/formatter "EEE, dd MMM yyyy HH:mm:ss 'GMT'" (jt/locale "en")) (jt/instant)))
  • 用jt/instant获取UTC时间,确保时区正确性
  • 显式指定格式化字符串,强制输出GMT标识
  • 英文Locale避免日期月份/星期的本地化问题

2. String-to-Sign遗漏必要请求头

Azure Shared Key认证要求所有x-ms-前缀的请求头必须加入String-to-Sign,且按字母顺序排列。原代码遗漏了请求中添加的x-ms-blob-type,导致签名不匹配。

修改get-authorization-header函数:

(defn get-authorization-header
  "Returns the Authorization header for a Blob Storage request."
  [account-name account-key request-date request-method content-type canonicalized-resource version blob-type]
  (let [string-to-sign (str request-method "\n"
                            "\n" ; Content-Encoding
                            "\n" ; Content-Language
                            "\n" ; Content-Length
                            "\n" ; Content-MD5
                            content-type "\n"
                            "\n" ; Date
                            "\n" ; If-Modified-Since
                            "\n" ; If-Match
                            "\n" ; If-None-Match
                            "\n" ; If-Unmodified-Since
                            "\n" ; Range
                            "x-ms-blob-type:" blob-type "\n"
                            "x-ms-date:" request-date "\n"
                            "x-ms-version:" version "\n"
                            canonicalized-resource)
        key-bytes (Base64/decodeBase64 account-key)
        key-spec (SecretKeySpec. key-bytes "HmacSHA256")
        mac (Mac/getInstance "HmacSHA256")
        _ (.init mac key-spec)
        signature-bytes (.doFinal mac (.getBytes string-to-sign "UTF-8")) ; 指定UTF-8编码避免字符集差异
        signature (Base64/encodeBase64String signature-bytes)
        auth-header (str "SharedKey " account-name ":" signature)]
    auth-header))
  • 按字母顺序添加x-ms-blob-type(b在d前,早于x-ms-date)
  • 显式指定字符串编码为UTF-8
  • 保留每个Header占位符的空行,即使对应Header为空

3. 请求体Base64编码冗余

上传普通文件时,Azure Blob接受原始字节流,无需Base64编码。原代码的编码会导致内容长度不匹配,触发认证错误。

修改请求体处理:

; 文本文件指定编码读取
file-content (slurp file-path :encoding "UTF-8")
; 二进制文件直接传File对象(clj-http自动处理字节流)
; file-content (clojure.java.io/file file-path)

(response (http/put request-url
                   {:body         file-content
                    :content-type content-type
                    :headers      {"Content-Type"   content-type
                                   "x-ms-date"      request-date
                                   "x-ms-version"   version
                                   "x-ms-blob-type" "BlockBlob"
                                   "Authorization"  auth-header}
                    :debug        true}))

4. 更新put-blob的函数调用

修改put-blob中调用get-authorization-header的部分,传入x-ms-blob-type参数:

auth-header (get-authorization-header account-name account-key request-date "PUT" content-type canon-resource version "BlockBlob")

内容的提问来源于stack exchange,提问作者Orlando J. Mendoza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:35:39