本文档主要介绍OSS签名过程中的常见问题及解决方法。

计算签名失败,报错“The request signature we calculated does not match the signature you provided”

  • 错误原因

    服务端计算的签名与客户端请求的签名不一致。

  • 问题分析
    OSS允许在Header中包含签名或在URL中包含签名,这两种签名方式的区别如下:
    签名方式 在Header中包含签名 在URL中包含签名
    是否支持设置expires 不支持 支持
    常用Method GET、POST、PUT、DELETE GET、PUT
    date格式 GMT格式 替换为expires,变成时间戳
    signature是否需要URL编码 不需要 需要
    注意 signature中所有加入计算的参数都需要包含在Header中,Header和signature需保持一致。有关要签名的Header,请参见在Header中包含签名
  • 解决方法

    以下代码用于调用API自签名时上传Object到OSS。

    #! /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"    
        self.url = 'http://{0}.{1}/{2}'.format(self.bk,self.ed,self.oj)
    
      # 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()
    • 请求头
      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
      说明 通过PUT上传时,signature计算的Content-type可以为application/x-www-form-urlencoded
    • 响应头
      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

通过微信小程序请求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处理。