作者:張醫博
背景
出現 signature 一般出現用戶端自簽名調 API 的操作中, signature 的計算稍微複雜點,建議最好用 SDK 來替代計算的過程和多樣性。如果業務強需求,先要讀懂如果計算 signature。
簽名分類
Header 頭中攜帶簽名。
https://help.aliyun.com/document_detail/31951.html?spm=a2c4g.11174283.6.1078.40437da2HGkyMHURL 中攜帶簽名。
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 ;
出現這個問題不區分什麼 SDK 都會出現,問題原因是由于用戶端發起的 HEAD 請求在通過 CDN 回原到 OSS 時,CDN 回原是用的 GET 請求,而 OSS 收到時就用 GET 請求方式去計算簽名,得到的結果肯定和用戶端計算不一緻,可以更新到阿裡雲 CDN 處理。以上分析隻适合上述場景。
問題可以通過 tcpdump 抓包或者 Wireshark 對比一下即可知道。