本文档主要介绍 OSS 签名过程中的常见问题及解决方法。
计算签名失败,报错“The request signature we calculated does not match the signature you provided”
OSS 允许在 Header 中包含签名或在 URL 中包含签名,这两种签名方式的区别如下:
Header | URL |
---|---|
不支持设置 expires | 支持设置 expires |
常用 Method 包括: GET、POST、PUT、DELETE | 常用 Method 包括:GET、PUT |
date 时间是 GMT 格式 | date 替换成 expires 变成时间戳 |
signature 不需要 URL 编码 | signature 需要 URL 编码 |
通过在 Header 或 URL 中自签名计算 signature 时, 经常遇到签名失败,报错 “The request signature we calculated does not match the signature you provided” 。以下 demo 演示了如何调用 API 自签名时上传 Object 到 OSS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | #! /us/bin/envy python #Author: hanli #Update: 2018 - 09 - 29 from optparse import OptionParser import urllib, urllib2 import datetime import base64 import hmac import sha import os import sys import time class Main(): # Initial input parse def __init__(self,options): self.ak = options.ak self.sk = options.sk self.ed = options.ed self.bk = options.bk self.fi = options.fi self.oj = options.objects self.left = '\033[1;31;40m' self.right = '\033[0m' self.types = "application/x-www-form-urlencoded" # Check client input parse def CheckParse(self): if (self.ak and self.sk and self.ed and self.bk and self.oj and self.fi) != None: if str(self.ak and self.sk and self.ed and self.bk and self.oj and self.fi): self.PutObject() else : self.ConsoleLog( "error" , "Input parameters cannot be empty" ) # GET local GMT time def GetGMT(self): SRM = datetime.datetime.utcnow() GMT = SRM.strftime( '%a, %d %b %Y %H:%M:%S GMT' ) return GMT # GET Signature def GetSignature(self): mac = hmac. new ( "{0}" .format(self.sk), "PUT\n\n{0}\n{1}\n/{2}/{3}" .format(self.types,self.GetGMT(),self.bk,self.oj), sha) Signature = base64.b64encode(mac.digest()) return Signature # PutObject def PutObject(self): try : with open(self.fi) as fd: files = fd.read() except Exception as e: self.ConsoleLog( "error" ,e) try : request = urllib2.Request(self.url, files) request.add_header( 'Host' , '{0}.{1}' .format(self.bk,self.ed)) request.add_header( 'Date' , '{0}' .format(self.GetGMT())) request.add_header( 'Authorization' , 'OSS {0}:{1}' .format(self.ak,self.GetSignature())) request.get_method = lambda: 'PUT' response = urllib2.urlopen(request,timeout= 10 ) fd.close() self.ConsoleLog(response.code,response.headers) except Exception,e: self.ConsoleLog( "error" ,e) # output error log def ConsoleLog(self,level=None,mess=None): if level == "error" : sys.exit( '{0}[ERROR:]{1}{2}' .format(self.left,self.right,mess)) else : sys.exit( '\nHTTP/1.1 {0} OK\n{1}' .format(level,mess)) if __name__ == "__main__" : parser = OptionParser() parser.add_option( "-i" ,dest= "ak" ,help= "Must fill in Accesskey" ) parser.add_option( "-k" ,dest= "sk" ,help= "Must fill in AccessKeySecrety" ) parser.add_option( "-e" ,dest= "ed" ,help= "Must fill in endpoint" ) parser.add_option( "-b" ,dest= "bk" ,help= "Must fill in bucket" ) parser.add_option( "-o" ,dest= "objects" ,help= "File name uploaded to oss" ) parser.add_option( "-f" ,dest= "fi" ,help= "Must fill localfile path" ) (options, args) = parser.parse_args() handler = Main(options) handler.CheckParse() |
请求头:
1 2 3 4 5 6 7 8 9 | PUT /yuntest HTTP/ 1.1 Accept-Encoding: identity Content-Length: 147 Connection: close User-Agent: Python-urllib/ 2.7 Date: Sat, 22 Sep 2018 04 : 36 : 52 GMT Host: yourBucket.oss-cn-shanghai.aliyuncs.com Content-Type: application/x-www-form-urlencoded Authorization: OSS B0g3mdt:lNCA4L0P43Ax |
响应头:
1 2 3 4 5 6 7 8 9 10 | HTTP/ 1.1 200 OK Server: AliyunOSS Date: Sat, 22 Sep 2018 04 : 36 : 52 GMT Content-Length: 0 Connection: close x-oss-request-id: 5BA5C6E4059A3C2F ETag: "D0CAA153941AAA1CBDA38AF" x-oss-hash-crc64ecma: 8478734191999037841 Content-MD5: 0MqhU5QbIp3Ujqqhy9o4rw== x-oss-server-time: 15 |
说明
- Signature 中所有加入计算的参数都要放在 Header 中,Header 和 Signature 需保持一致。有关要签名的 Header,请参见在 Header 中包含签名。
- 通过 PUT 上传时,signature 计算的 Content-type 可以为 application/x-www-form-urlencoded 。
- 通过 Header 方式进行签名认证时无法设置 expires。只有通过 SDK 和控制台设置签名 URL 时可以设置 expires。
通过微信小程序请求 OSS 返回签名失败,通过浏览器请求 OSS 返回签名正常
通过浏览器访问时的 HTTP 抓包数据如下图所示。

- 通过反复对比 403 和 200 的抓包数据发现通过微信小程序发出的 HTTP 请求和浏览器发起的 HTTP 请求的 URL 、signature、expires 相同,区别在于微信小程序携带了 Content-type ,而通过浏览器的请求没有携带 Content-type。
- signature 计算时没有包含 Content-tpye ,而微信小程序发起的请求携带了 Content-type 。OSS 收到请求后会按照携带了 Content-type 的方式来计算 signature ,导致计算结果不一致。
遇到类似问题,建议抓包排查。如果 OSS 请求 Header 中携带了 Content-type,则 signature 计算也要加上 Content-type。
通过 OSS 多个语言版本 SDK 测试发现,结合 CDN 使用 OSS 时,客户端使用 CDN 域名计算 signature,并发起 Head 请求时,OSS 收到请求后返回 403 ;

通过 tcpdump 抓包或者 Wireshark 对比得知,由于客户端发起的 Head 请求在通过 CDN 回源到 OSS 时,CDN 回源用的是 GET 请求,OSS 接收到该请求时用 GET 请求方式来计算 signature,得到的结果与客户端计算不一致,该问题可以升级阿里云 CDN 处理。