Azure Put Blob API签名文件大小不匹配致认证失败问题求助
嘿,我一眼就看出问题出在哪了——你遇到的签名验证失败,根源是本地计算的文件大小和实际发送给Azure的请求内容长度不匹配!
问题原因拆解
你用os.stat()拿到的19337是本地PDF文件的原始大小,但你在调用requests.put()时用了files参数上传文件。这个参数会自动把请求体封装成multipart/form-data格式,相当于给你的PDF文件套了一层“包装”——里面包含了边界标识、表单字段的头信息等额外内容。这就导致实际发送的HTTP请求Content-Length变成了19497,而你签名时用的是原始文件大小19337,Azure服务端验证签名时会用实际收到的内容长度来计算,两边对不上,自然就认证失败了。
解决办法(两种方案,推荐第一种)
方案1:直接发送原始文件内容(最可靠)
放弃用files参数,直接把文件的原始二进制内容作为请求的data参数发送。这样请求体就是纯PDF内容,Content-Length会和os.stat()获取的大小完全一致,签名验证就能通过。
修改后的代码如下:
import os import datetime import base64 import hmac import hashlib import requests storageAccountName = '<storage account name>' storageContainerName = "<container_name>" storageKey='<key>' # 注意路径用r前缀避免转义问题 fd = r"C:\<path>\<to>\<file_to_upload>.pdf" URI = f'https://{storageAccountName}.blob.core.windows.net/{storageContainerName}/<blob_file_name.pdf>' version = '2017-07-29' date = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT") if os.path.isfile(fd): file_info = os.stat(fd) file_size = file_info.st_size # 构造规范化请求字符串 canonicalized_headers = ( f"PUT\n\n\n{file_size}\n\napplication/pdf\n\n\n\n\n\n\n" f"x-ms-blob-type:BlockBlob\n" f"x-ms-date:{date}\n" f"x-ms-version:{version}\n" ) canonicalized_resource = f"/{storageAccountName}/{storageContainerName}/<blob_file_name.pdf>" canonicalizedString = canonicalized_headers + canonicalized_resource # 计算签名 storage_account_key = base64.b64decode(storageKey) byte_canonicalizedString = canonicalizedString.encode('utf-8') signature = base64.b64encode(hmac.new( key=storage_account_key, msg=byte_canonicalizedString, digestmod=hashlib.sha256 ).digest()) # 构造请求头 headers = { 'x-ms-blob-type': "BlockBlob", 'x-ms-date': date, 'x-ms-version': version, 'Authorization': f'SharedKey {storageAccountName}:{signature.decode("utf-8")}', 'Content-Length': str(file_size), 'Content-Type': "application/pdf" } # 直接读取文件内容作为data发送,不要用files参数 with open(fd, 'rb') as file: file_content = file.read() result = requests.put(url=URI, headers=headers, data=file_content) print(result.content)
方案2:手动计算multipart请求体的总长度(不推荐)
如果你非要用files参数,就得手动计算整个multipart请求体的长度(包括边界、表单头、文件内容的总和),然后把这个值放到签名的规范化字符串和Content-Length头里。但这种方法很容易出错,因为边界字符串是requests自动生成的,你需要提前捕获它,计算起来非常麻烦,所以强烈建议用方案1。
额外建议
其实更省心的方式是使用Azure官方的Python SDK azure-storage-blob,它会自动处理签名、请求头、内容长度这些细节,完全不用你手动构造请求,能避免很多类似的低级错误。
内容的提问来源于stack exchange,提问作者RoT

