利用STS技術實現對象存儲的鑑權

背景介紹

目前很多網站都是通過將圖片存儲類似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需要先做如下操作:

  1. 創建RAM用戶
  2. 創建權限策略
  3. 創建角色
    具體的詳細步驟可以參考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

References

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章