天天看點

OSS signature 計算

作者:張醫博

背景

出現 signature 一般出現用戶端自簽名調 API 的操作中, signature 的計算稍微複雜點,建議最好用 SDK 來替代計算的過程和多樣性。如果業務強需求,先要讀懂如果計算 signature。

簽名分類

Header 頭中攜帶簽名。

https://help.aliyun.com/document_detail/31951.html?spm=a2c4g.11174283.6.1078.40437da2HGkyMH

URL 中攜帶簽名。

https://help.aliyun.com/document_detail/31952.html?spm=a2c4g.11186623.6.1079.7b61734cNAir2U

簽名差別

Header URL
不支援設定 expires 支援設定 expires
常用 method GET、POST、PUT 常用 method GET、PUT
date 時間是 GMT 格式 date 替換成 expires 變成時間戳
signature 不需要 URL encode signature 需要 URL encode

計算簽名 demo

當客戶通過 header 或者 URL 中自簽名計算 signature 時,經常會遇到計算簽名失敗 “The request signature we calculated does not match the signature you provided” ,可以參考以下 demo 示範了如何調用 API 自簽名時上傳 Object 到 OSS,注意簽名和 header 加入的内容。

#! /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           

響應頭:

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 一緻。
  • PUT 上傳時,Signature 計算的 Content-Type 必須是 application/x-www-form-urlencoded 。
  • 通過header 方式進行簽名認證時無法設定過期時間。目前隻有 SDK 、URL 簽名支援設定過期時間。

使用問題

案例:通過微信小程式請求 OSS 傳回簽名失敗,通過浏覽器正常

分析:

  • 隻要通過浏覽器通路,鑒權通過就證明 OSS 的簽名校驗是正常的沒有問題,可以先排除掉 OSS 端。
  • 用戶端一定要在微信小程式上部署 HTTP 抓包,對後續分析很重要,抓包中可以看到所有的請求頭和請求參數。
  • 通過浏覽器通路時的 HTTP 抓包。
    OSS signature 計算
  • 通過 403 和 200 的抓包反複對比發現,通過小程式發出的 HTTP 請求和浏覽器發起的 HTTP 請求的 URL 、signature、expires 都一樣,唯一的差別就是微信小程式攜帶了 Content-type ,而通過 Chrom 的請求是沒有攜帶 Content-type,懷疑矛頭指向了這裡。
  • 經過代碼确認,發現 signature 計算時是沒有包含 Content-tpye 頭的,而小程式發起的請求攜帶的 Content-tpye ,OSS 收到後會按照攜帶了 Content-tpye 去計算 signature ,是以每次計算都不一樣。

遇到類似問題,抓包是最能快速看到問題的。同時也必須要了解下 OSS 請求 header 中攜帶了 Content-tpye ,那麼 signature 計算就要加上 Content-tpye ,保持一緻。

案例:通過多個語言版本 OSS SDK 測試,在使用 CDN 結合 OSS 用法時,用戶端使用 CDN 域名計算 signature,發起 HEAD 請求,OSS 收到後傳回 403 ;

OSS signature 計算

出現這個問題不區分什麼 SDK 都會出現,問題原因是由于用戶端發起的 HEAD 請求在通過 CDN 回原到 OSS 時,CDN 回原是用的 GET 請求,而 OSS 收到時就用 GET 請求方式去計算簽名,得到的結果肯定和用戶端計算不一緻,可以更新到阿裡雲 CDN 處理。以上分析隻适合上述場景。

問題可以通過 tcpdump 抓包或者 Wireshark 對比一下即可知道。

繼續閱讀