背景介紹
目前很多網站都是通過將圖片存儲類似S3(AWS)或者OSS(阿里雲)上這種方式來進行存儲,這樣存儲成本低,擴展性和可靠性都足夠好。而這種方式在存儲和獲取的時候就會涉及鑑權的相關問題。以阿里云爲例,我們可以通過STS(Security Token Service)來進行授權,獲取臨時的token,這樣就可以避免使用公共讀的方式讓用戶獲取圖片或者其它對象,增強了系統的整體安全性
架構
STS的架構圖如下:
- App服務器就是我們自己的後端服務,其持有阿里雲的RAM用戶AK(AccessKeyId和AccessKeySecret)
- App客戶端使用STS credentials去請求阿里雲的OSS服務。在這裏需要說明的是App客戶端有兩種請求方式,一種是將credentials放在headers當中;另外一種是將credentials包含在URL當中。其中第二種方式更加靈活,因爲客戶端不需要做任何修改,而我們的後端服務可以直接生成帶有credentials的URL給客戶端。
配置STS
配置STS需要先做如下操作:
- 創建RAM用戶
- 創建權限策略
- 創建角色
具體的詳細步驟可以參考STS臨時授權訪問OSS
示例代碼
請求獲取STS credentials
def get_sts_credentials():
"""get credentials from Aliyun STS service
refer to https://help.aliyun.com/document_detail/100624.html
"""
# OSS_STS_KEY_ID爲第一步生成的RAM用戶時獲取到的AccessKeyID,OSS_STS_KEY_SECRET對應的是RAM用戶的AccessKeySecret
# OSS_REGION_ID即阿里雲定義的region id, 比如:cn-beijing
client = AcsClient(
settings.OSS_STS_KEY_ID, settings.OSS_STS_KEY_SECRET, settings.OSS_REGION_ID)
request = AssumeRoleRequest()
request.set_accept_format('json')
# OSS_ROLE_ARN爲我們在第三步生成的role的ARN,可以通過阿里雲的控制檯來查詢
request.set_RoleArn(settings.OSS_ROLE_ARN)
# 自定義的session name
request.set_RoleSessionName(settings.ROLE_SESSION_NAME)
request.set_DurationSeconds(3600)
response = client.do_action_with_exception(request)
body = json.loads(str(response, encoding='utf-8'))
return body['Credentials']
生成帶有credentials的URL
後端服務通過上節的方式獲取到STS credentials之後,一種方式是將該credentials發送給客戶端,由客戶端去組成帶有credentials的http請求,然後再發送給阿里雲的OSS服務,這就需要客戶端針對此種情況做些變動,這種情況就要求用戶必須強制升級到新版本的客戶端纔可以,顯然這並不是好的選項;另外一種方式是我們在服務器端直接生成帶有credentials的URL,下面我們就來看下如何實現此種方式。
- 針對簽名字符串生成數字簽名
要生成數字簽名,我們首先要明確針對什麼字符串進行簽名,這裏吐槽下阿里的文檔,寫得不僅模糊,而且是錯誤的,其文檔說要針對如下字符串進行簽名:
VERB + "\n"
+ CONTENT-MD5 + "\n"
+ CONTENT-TYPE + "\n"
+ EXPIRES + "\n"
+ CanonicalizedOSSHeaders
+ CanonicalizedResource)
但是,實際經過測試應該針對如下字符串進行簽名:
VERB + "\n\n\n"
+ EXPIRES + "\n"
+ TARGET_URL
- VERB, 就是http method,比如"GET"
- EXPIRES, 是我們自定義的過期時間戳,可以同當前時間戳 + 過期秒數來生成,注意這裏必須轉爲爲整數字符串
- TARGET_URL, 要請求的url包含parameter,如下:
target_url = f"/{bucket_name}/{object_name}?security-token={security_token}"
生成數字簽名的代碼片段:
signature_str = (
"GET" + "\n\n\n"
+ content
+ content_type
+ expires
+ target_url)
logger.info(f"signature string: {signature_str}")
h = hmac.new(
bytes(secret, encoding='utf-8'),
bytes(signature_str, encoding='utf-8'),
hashlib.sha1
)
signature = base64.b64encode(h.digest())
- 生成對應的URL
有了數字簽名,我們就可以組裝我們的URL了,如下:
params = {"OSSAccessKeyId": credentials["AccessKeyId"],
"Expires": exp_ts,
"Signature": signature,
"security-token": credentials['SecurityToken']}
query_str = urllib.parse.urlencode(params)
url = url + "?" + query_str