微信小程序可以將圖片、文檔、視頻等文件上傳到OSS,實現文件的云端存儲和分發。
方案概覽
微信小程序上傳文件到OSS的過程如下:
要實現微信小程序上傳文件到OSS,只需兩步:
配置服務端:在ECS,創建一個實例,用于從STS服務獲取一個臨時訪問憑證,然后使用臨時訪問憑證為微信小程序生成上傳文件到OSS所需的憑證簽名。
配置微信小程序:在小程序平臺,使用Bucket域名配置微信小程序的合法域名,確保小程序向OSS發送文件的請求不會被微信攔截;在微信小程序端,實現從ECS獲取簽名并使用簽名上傳文件到OSS的功能。
操作步驟
步驟一:配置服務端
在實際部署時,如果您已經有自己的ECS服務器,則無需創建該ECS實例,可直接進行第2步ECS服務端計算簽名。
創建并連接ECS實例。
創建ECS實例
請您進入自定義購買頁面,并根據如下各模塊的內容,創建或選擇購買ECS實例所需的基礎資源。
選擇地域 & 付費類型
根據業務需求,選擇合適的付費類型。本文選擇按量付費模式,此模式操作相對靈活。
基于業務場景對時延的要求,選擇地域。通常來說離ECS實例的物理距離越近,網絡時延越低,訪問速度越快。本文以選擇華東1(杭州)為例。
創建專有網絡VPC & 交換機
創建VPC時,請您選擇和ECS相同的地域,并根據業務需求規劃網段。本文以創建華東1(杭州)地域的VPC和交換機為例。創建完畢后返回ECS購買頁,刷新并選擇VPC及交換機。
說明創建VPC時,可同時創建交換機。
選擇規格 & 鏡像
選擇實例的規格及鏡像,鏡像為實例確定安裝的操作系統及版本。本文選擇的實例規格為
ecs.e-c1m1.large
,在滿足測試需求的同時,價格較為實惠。鏡像為公共鏡像Alibaba Cloud Linux 3.2104 LTS 64位
。
選擇存儲
為ECS實例選擇系統盤,并按需選擇數據盤。本文實現簡單Web系統搭建,只需要系統盤存儲操作系統,無需數據盤。
綁定公網IP
本實例需要支持公網訪問。為了簡化操作,本文選擇直接為實例分配公網IP。您也可以在創建實例后,為實例綁定彈性公網IP,具體操作,請參見將EIP綁定至ECS實例。
說明若未綁定公網IP,將無法使用SSH或RDP通過公網直接訪問實例,也無法通過公網驗證實例中Web服務的搭建。
本文選擇按使用流量的帶寬計費模式。此模式只需為所消耗的公網流量付費。更多信息,請參見公網帶寬計費。
創建安全組
為實例創建安全組。安全組是一種虛擬網絡防火墻,能夠控制ECS實例的出入流量。創建時,需要設置放行以下指定端口,便于后續訪問ECS實例。
端口范圍:SSH(22)、RDP(3389)、HTTP(80)、HTTPS(443)。
說明端口范圍處選中的是ECS實例上運行的應用需開放的端口。
此處創建的安全組默認設置0.0.0.0/0作為源的規則。0.0.0.0/0表示允許全網段設備訪問指定的端口,如果您知道請求端的IP地址,建議后續設置為具體的IP范圍。具體操作,請參見修改安全組規則。
創建密鑰對
密鑰對可作為登錄時證明個人身份的安全憑證,創建完成后,必須下載私鑰,以供后續連接ECS實例時使用。創建完畢后返回ECS購買頁,刷新并選擇密鑰對。
root
具有操作系統的最高權限,使用root
作為登錄名可能會導致安全風險,建議您選擇ecs-user
作為登錄名。說明創建密鑰對后,私鑰會自動下載,請您關注瀏覽器的下載記錄,保存
.pem
格式的私鑰文件。
創建并查看ECS實例
創建或選擇好ECS實例所需的基礎資源后,勾選《云服務器ECS服務條款》、《云服務器ECS退訂說明》,單擊確認下單。在提示成功的對話框中,單擊管理控制臺,即可在控制臺查看到創建好的ECS實例。請您保存以下數據,以便在后續操作中使用。
實例ID:便于在實例列表中查詢到該實例。
地域:便于在實例列表中查詢到該實例。
公網IP地址:便于在后續使用ECS實例時,做Web服務的部署結果驗證。
連接ECS實例
在云服務器ECS控制臺的實例列表頁面,根據地域、實例ID找到創建好的ECS實例,單擊操作列下的遠程連接。
在遠程連接對話框中,單擊通過Workbench遠程連接對應的立即登錄。
在登錄實例對話框中,選擇認證方式為SSH密鑰認證,用戶名為
ecs-user
,輸入或上傳創建密鑰對時下載的私鑰文件,單擊確定,即可登錄ECS實例。說明私鑰文件在創建密鑰對時自動下載到本地,請您關注瀏覽器的下載記錄,查找
.pem
格式的私鑰文件。顯示如下頁面后,即說明您已成功登錄ECS實例。
ECS服務端計算簽名。
重要服務端提供了以下兩種方式獲取STS臨時訪問憑證并計算簽名。
ECS扮演RAM角色獲取STS臨時訪問憑證計算簽名:服務端不保留AK信息,采用ECS扮演RAM角色的方式,去訪問STS服務以獲取臨時訪問憑證并計算簽名,最大程度上降低了AK信息泄露的風險,安全性較高。
RAM用戶獲取STS臨時訪問憑證計算簽名:服務端需要保留AK信息,通過獲取配置于服務端環境變量里的RAM用戶AK信息,進而訪問STS服務獲取臨時訪問憑證并計算簽名,安全性較低。
ECS扮演RAM角色獲取STS臨時訪問憑證計算簽名
ECS綁定RAM角色。
進入RAM訪問控制的創建角色頁面。
在創建角色頁面勾選阿里云服務,單擊下一步。
勾選普通服務角色,填寫角色名稱并選擇受信服務為云服務器后,單擊完成,即可完成創建。
創建完畢后單擊
,在權限策略中勾選ALiyunOSSFullAccess
權限后單擊確認新增授權。說明倘若您對訪問控制權限較為熟悉,那么可以直接進入創建權限策略頁面,按需創建更為精準的自定義授權,隨后將其授予RAM角色,以此防止權限出現冗余情況。
進入云服務器ECS的實例頁面,在頁面上方選擇ECS實例所處地域,然后在實例頁面單擊目標實例右側按鈕,單擊授予/收回RAM角色。
在授予/收回RAM角色彈出框選擇目標RAM角色,完成ECS綁定RAM角色。
服務端計算簽名。
Java
請參考以下示例完成Java服務端的V4簽名計算。完整示例工程請部署upload_server.zip。
配置依賴。
<!-- https://mvnrepository.com/artifact/com.aliyun/credentials-java --> <dependency> <groupId>com.aliyun</groupId> <artifactId>credentials-java</artifactId> <version>0.3.4</version> </dependency> <dependency> <groupId>com.aliyun.kms</groupId> <artifactId>kms-transfer-client</artifactId> <version>0.1.0</version> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.17.4</version> </dependency>
API接口示例。
package com.example.demo.controller; import com.example.demo.util.ECSGenerateSignature; import com.example.demo.util.RAMGenerateSignature; import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class VxController { /** * 使用ECS扮演RAM角色方式獲取臨時訪問憑證,計算簽名信息,返回小程序端。 * @return * @throws JsonProcessingException */ @GetMapping("/generate_signature") public String generate_signature() throws JsonProcessingException { ECSGenerateSignature ecsGenerateSignature = new ECSGenerateSignature(); return ecsGenerateSignature.getSignature(); } }
簽名信息工具類示例。
package com.example.demo.util; import com.aliyun.credentials.models.CredentialModel; import com.aliyun.oss.common.auth.Credentials; import com.aliyun.oss.common.auth.CredentialsProvider; import com.aliyun.oss.common.auth.DefaultCredentials; import com.aliyun.oss.common.utils.BinaryUtil; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * ECS扮演RAM角色方式獲取STS臨時訪問憑證計算簽名 */ public class ECSGenerateSignature { public String getSignature() throws JsonProcessingException { com.aliyun.credentials.models.Config config = new com.aliyun.credentials.models.Config(); config.setType("ecs_ram_role"); //固定值無需更改 config.setRoleName("roleName"); //請替換為ECS扮演的角色名稱 final com.aliyun.credentials.Client credentialsClient = new com.aliyun.credentials.Client(config); //創建一個匿名內部類實現CredentialsProvider接口,用于提供阿里云OSS操作所需的憑證信息 CredentialsProvider credentialsProvider = new CredentialsProvider() { @Override public void setCredentials(Credentials credentials) { } @Override public Credentials getCredentials() { CredentialModel credential = credentialsClient.getCredential(); return new DefaultCredentials(credential.getAccessKeyId(), credential.getAccessKeySecret(), credential.getSecurityToken()); } }; String accessKeyId = credentialsProvider.getCredentials().getAccessKeyId(); //獲取AK String secretAccessKey = credentialsProvider.getCredentials().getSecretAccessKey(); //獲取SK String securityToken = credentialsProvider.getCredentials().getSecurityToken(); //獲取Token //格式化請求日期 long now = System.currentTimeMillis() / 1000; ZonedDateTime dtObj = ZonedDateTime.ofInstant(Instant.ofEpochSecond(now), ZoneId.of("UTC")); ZonedDateTime dtObjPlus3h = dtObj.plusHours(3); //請求時間 DateTimeFormatter dtObj1Formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"); String dtObj1 = dtObj.format(dtObj1Formatter); //請求日期 DateTimeFormatter dtObj2Formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); String dtObj2 = dtObj.format(dtObj2Formatter); //請求過期時間 DateTimeFormatter expirationTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); String expirationTime = dtObjPlus3h.format(expirationTimeFormatter); // 創建policy ObjectMapper mapper = new ObjectMapper(); Map<String, Object> policy = new HashMap<>(); policy.put("expiration", expirationTime); List<Object> conditions = new ArrayList<>(); Map<String, String> bucketCondition = new HashMap<>(); bucketCondition.put("bucket", "bucketname"); //請替換為目標Bucket名稱 conditions.add(bucketCondition); Map<String, String> signatureVersionCondition = new HashMap<>(); signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256"); conditions.add(signatureVersionCondition); Map<String, String> credentialCondition = new HashMap<>(); credentialCondition.put("x-oss-credential", accessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request"); // 替換為實際的 access key id conditions.add(credentialCondition); Map<String, String> token = new HashMap<>(); token.put("x-oss-security-token", securityToken); conditions.add(token); Map<String, String> dateCondition = new HashMap<>(); dateCondition.put("x-oss-date", dtObj1); conditions.add(dateCondition); policy.put("conditions", conditions); String jsonPolicy = mapper.writeValueAsString(policy); //構造待簽名字符串(StringToSign) String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes())); //計算SigningKey byte[] dateKey = hmacsha256(("aliyun_v4" + secretAccessKey).getBytes(), dtObj2); byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou"); byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss"); byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request"); //計算Signature byte[] result = hmacsha256(signingKey, stringToSign); String signature = BinaryUtil.toHex(result); Map<String, String> messageMap = new HashMap<>(); messageMap.put("security_token", securityToken); messageMap.put("signature", signature); messageMap.put("x_oss_date", dtObj1); messageMap.put("x_oss_credential", accessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request"); messageMap.put("x_oss_signature_version", "OSS4-HMAC-SHA256"); messageMap.put("policy", stringToSign); ObjectMapper objectMapper = new ObjectMapper(); //打印返回至客戶端的簽名信息 System.out.println(objectMapper.writeValueAsString(messageMap)); return objectMapper.writeValueAsString(messageMap); } /** * 使用HMAC-SHA256算法計算給定密鑰和數據的哈希值的靜態方法 * @param key * @param data * @return */ public static byte[] hmacsha256(byte[] key,String data){ try { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(secretKeySpec); byte[] hmacBytes = mac.doFinal(data.getBytes()); return hmacBytes; }catch (Exception e){ throw new RuntimeException("Failed to calculate HMAC-SHA256", e); } } }
Python
請參考以下示例完成Python服務端的V4簽名計算。
配置依賴。
sudo pip install oss2 sudo pip install alibabacloud_credentials
代碼示例。
from flask import Flask, jsonify import base64 import hmac import hashlib import os import datetime import json import time from alibabacloud_credentials.client import Client as CredClient from alibabacloud_credentials.models import Config as CredConfig app = Flask(__name__) def hmacsha256(key, data): """ 計算HMAC-SHA256哈希值的函數 :param key: 用于計算哈希的密鑰,字節類型 :param data: 要進行哈希計算的數據,字符串類型 :return: 計算得到的HMAC-SHA256哈希值,字節類型 """ try: mac = hmac.new(key, data.encode(), hashlib.sha256) hmacBytes = mac.digest() return hmacBytes except Exception as e: raise RuntimeError(f"Failed to calculate HMAC-SHA256 due to {e}") @app.route('/generate_signature', methods=['GET']) def generate_signature(): """ 處理生成簽名信息的請求,執行相關邏輯流程,包括獲取環境變量、創建策略、構造待簽名字符串、計算簽名等操作, 并返回生成的簽名信息。 :return: JSON格式的響應,包含簽名信息的字典,格式如下: { "policy": "policy字符串", "x-oss-signature-version": "OSS4-HMAC-SHA256", "x-oss-credential": "accesskeyid/日期/cn-hangzhou/oss/aliyun_v4_request", "x-oss-date": "請求時間", "signature": "簽名", "security_token": "安全令牌" } """ credentialConfig = CredConfig( # 固定值無需更改 type='ecs_ram_role', # 請替換為ECS扮演的角色名稱 role_name='role_name' ) credentialsClient = CredClient(credentialConfig) credential = credentialsClient.get_credential() # 獲取AK accesskeyid = credential.access_key_id # 獲取SK accesskeysecret = credential.access_key_secret # 獲取Token security_token = credential.security_token now = int(time.time()) # 將時間戳轉換為datetime對象 dt_obj = datetime.datetime.utcfromtimestamp(now) # 在當前時間增加3小時,設置為請求的過期時間 dt_obj_plus_3h = dt_obj + datetime.timedelta(hours=3) # 請求時間 dt_obj_1 = dt_obj.strftime('%Y%m%dT%H%M%S') + 'Z' # 請求日期 dt_obj_2 = dt_obj.strftime('%Y%m%d') # 請求過期時間 expiration_time = dt_obj_plus_3h.strftime('%Y-%m-%dT%H:%M:%S.000Z') # 步驟1:創建policy。 policy = { "expiration": expiration_time, "conditions": [ {"bucket": "bucket_name"}, #請替換為目標Bucket名稱 {"x-oss-signature-version": "OSS4-HMAC-SHA256"}, {"x-oss-credential": f"{accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request"}, {"x-oss-security-token": security_token}, {"x-oss-date": dt_obj_1}, ] } policy_str = json.dumps(policy).strip() # 步驟2:構造待簽名字符串(StringToSign) stringToSign = base64.b64encode(policy_str.encode()).decode() # 步驟3:計算SigningKey dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).encode(), dt_obj_2) dateRegionKey = hmacsha256(dateKey, "cn-hangzhou") dateRegionServiceKey = hmacsha256(dateRegionKey, "oss") signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request") # 步驟4:計算Signature result = hmacsha256(signingKey, stringToSign) signature = result.hex() return jsonify({ "policy": stringToSign, #表單域 "x_oss_signature_version": "OSS4-HMAC-SHA256", #指定簽名的版本和算法,固定值為OSS4-HMAC-SHA256 "x_oss_credential": f"{accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request", #指明派生密鑰的參數集 "x_oss_date": dt_obj_1, #請求的時間 "signature": signature, #簽名認證描述信息 "security_token": security_token #安全令牌 }) if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)
RAM用戶獲取STS臨時訪問憑證計算簽名
Java
請參考以下示例完成Java服務端的V4簽名計算。完整示例工程請部署upload_server.zip。
配置依賴。
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.17.4</version> </dependency>
配置環境變量。
說明<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
請分別替換為RAM用戶的AccessKey ID、AccessKeySecret。如何創建AccessKey ID和AccessKeySecret請參見創建AccessKey。<ROLE_ARN>
請替換為目標角色ARN,在角色頁面單擊目標RAM角色名稱,然后在基本信息區域查看對應的ARN。如何創建角色請參見創建RAM角色并授權。
macOS/Linux/Unix系統。
export OSS_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID> export OSS_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET> export OSS_STS_ROLE_ARN=<ROLE_ARN>
Windows系統。
set OSS_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID> set OSS_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET> set OSS_STS_ROLE_ARN=<ROLE_ARN>
API接口示例。
package com.example.demo.controller; import com.example.demo.util.ECSGenerateSignature; import com.example.demo.util.RAMGenerateSignature; import com.fasterxml.jackson.core.JsonProcessingException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class VxController { /** * 使用環境變量中的AK獲取臨時訪問憑證,計算簽名信息,返回小程序端。 * @return * @throws JsonProcessingException */ @GetMapping("/generate_signature") public String generate_signature() throws JsonProcessingException { RAMGenerateSignature ramGenerateSignature = new RAMGenerateSignature(); return ramGenerateSignature.getSignature(); } }
簽名信息工具類示例。
package com.example.demo.util; import com.aliyun.oss.common.utils.BinaryUtil; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.auth.sts.AssumeRoleRequest; import com.aliyuncs.auth.sts.AssumeRoleResponse; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 使用環境變量中的AK獲取臨時訪問憑證計算簽名 */ public class RAMGenerateSignature { public String getSignature() throws JsonProcessingException { //獲取發送STS請求基礎信息 String accessKeyId = System.getenv("OSS_ACCESS_KEY_ID"); //環境變量中獲取access_key_id String accessKeySecret = System.getenv("OSS_ACCESS_KEY_SECRET"); //環境變量中獲取access_key_secret String roleArnForOssUpload = System.getenv("OSS_STS_ROLE_ARN"); //環境變量中獲取ARN String regionId = "cn-hangzhou"; //發起STS請求所在的地域 String roleSessionName = "<YOUR_ROLE_SESSION_NAME>"; //色會話名稱,用來區分不同的令牌,可自定義 Long durationSeconds = 3600L; //臨時訪問憑證的有效時間 //初始化客戶端 DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); IAcsClient client = new DefaultAcsClient(profile); AssumeRoleRequest request = new AssumeRoleRequest(); request.setRoleArn(roleArnForOssUpload); request.setRoleSessionName(roleSessionName); request.setDurationSeconds(durationSeconds); //定義STS臨時訪問憑證變量 String STSaccessKeyId = null; String STSsecretAccessKey = null; String securityToken = null; try { AssumeRoleResponse response = client.getAcsResponse(request); //將請求返回的STS臨時訪問憑證賦值到自定義變量中 STSaccessKeyId = response.getCredentials().getAccessKeyId(); STSsecretAccessKey = response.getCredentials().getAccessKeySecret(); securityToken = response.getCredentials().getSecurityToken(); } catch (ClientException e) { e.printStackTrace(); } //格式化請求日期 long now = System.currentTimeMillis() / 1000; ZonedDateTime dtObj = ZonedDateTime.ofInstant(Instant.ofEpochSecond(now), ZoneId.of("UTC")); ZonedDateTime dtObjPlus3h = dtObj.plusHours(3); //請求時間 DateTimeFormatter dtObj1Formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"); String dtObj1 = dtObj.format(dtObj1Formatter); //請求日期 DateTimeFormatter dtObj2Formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); String dtObj2 = dtObj.format(dtObj2Formatter); //請求過期時間 DateTimeFormatter expirationTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); String expirationTime = dtObjPlus3h.format(expirationTimeFormatter); // 創建policy ObjectMapper mapper = new ObjectMapper(); Map<String, Object> policy = new HashMap<>(); policy.put("expiration", expirationTime); List<Object> conditions = new ArrayList<>(); Map<String, String> bucketCondition = new HashMap<>(); bucketCondition.put("bucket", "bucketname"); //請替換為目標bucket名稱 conditions.add(bucketCondition); Map<String, String> signatureVersionCondition = new HashMap<>(); signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256"); conditions.add(signatureVersionCondition); Map<String, String> credentialCondition = new HashMap<>(); credentialCondition.put("x-oss-credential", STSaccessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request"); // 替換為實際的 access key id conditions.add(credentialCondition); Map<String, String> token = new HashMap<>(); token.put("x-oss-security-token", securityToken); conditions.add(token); Map<String, String> dateCondition = new HashMap<>(); dateCondition.put("x-oss-date", dtObj1); conditions.add(dateCondition); policy.put("conditions", conditions); String jsonPolicy = mapper.writeValueAsString(policy); //構造待簽名字符串(StringToSign) String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes())); //計算SigningKey byte[] dateKey = hmacsha256(("aliyun_v4" + STSsecretAccessKey).getBytes(), dtObj2); byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou"); byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss"); byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request"); //計算Signature byte[] result = hmacsha256(signingKey, stringToSign); String signature = BinaryUtil.toHex(result); Map<String, String> messageMap = new HashMap<>(); messageMap.put("security_token", securityToken); messageMap.put("signature", signature); messageMap.put("x_oss_date", dtObj1); messageMap.put("x_oss_credential", STSaccessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request"); messageMap.put("x_oss_signature_version", "OSS4-HMAC-SHA256"); messageMap.put("policy", stringToSign); ObjectMapper objectMapper = new ObjectMapper(); //打印返回至客戶端的簽名信息 // System.out.println(objectMapper.writeValueAsString(messageMap)); return objectMapper.writeValueAsString(messageMap); } /** * 使用HMAC-SHA256算法計算給定密鑰和數據的哈希值的靜態方法 * @param key * @param data * @return */ public static byte[] hmacsha256(byte[] key,String data){ try { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(secretKeySpec); byte[] hmacBytes = mac.doFinal(data.getBytes()); return hmacBytes; }catch (Exception e){ throw new RuntimeException("Failed to calculate HMAC-SHA256", e); } } }
Python
請參考以下示例完成Python服務端的V4簽名計算。
配置依賴。
pip install oss2 pip install alibabacloud_tea_openapi alibabacloud_sts20150401 alibabacloud_credentials
配置環境變量。
說明<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
請分別替換為RAM用戶的AccessKey ID、AccessKeySecret。如何創建AccessKey ID和AccessKeySecret請參見創建AccessKey。<ROLE_ARN>
請替換為目標角色ARN,在角色頁面單擊目標RAM角色名稱,然后在基本信息區域查看對應的ARN。如何創建角色請參見創建RAM角色并授權。
macOS/Linux/Unix
export OSS_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID> export OSS_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET> export OSS_STS_ROLE_ARN=<ROLE_ARN>
Windows
set OSS_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID> set OSS_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET> set OSS_STS_ROLE_ARN=<ROLE_ARN>
代碼示例。
from flask import Flask, jsonify import base64 import hmac import hashlib import os import datetime import json import time from alibabacloud_tea_openapi.models import Config from alibabacloud_sts20150401.client import Client as Sts20150401Client from alibabacloud_sts20150401 import models as sts_20150401_models from alibabacloud_credentials.client import Client as CredentialClient import os app = Flask(__name__) def hmacsha256(key, data): """ 計算HMAC-SHA256哈希值的函數 :param key: 用于計算哈希的密鑰,字節類型 :param data: 要進行哈希計算的數據,字符串類型 :return: 計算得到的HMAC-SHA256哈希值,字節類型 """ try: mac = hmac.new(key, data.encode(), hashlib.sha256) hmacBytes = mac.digest() return hmacBytes except Exception as e: raise RuntimeError(f"Failed to calculate HMAC-SHA256 due to {e}") @app.route('/generate_signature', methods=['GET']) def generate_signature(): """ 處理生成簽名信息的請求,執行相關邏輯流程,包括獲取環境變量、創建策略、構造待簽名字符串、計算簽名等操作, 并返回生成的簽名信息。 :return: JSON格式的響應,包含簽名信息的字典,格式如下: { "policy": "policy字符串", "x-oss-signature-version": "OSS4-HMAC-SHA256", "x-oss-credential": "accesskeyid/日期/cn-hangzhou/oss/aliyun_v4_request", "x-oss-date": "請求時間", "signature": "簽名", "security_token": "安全令牌" } """ access_key_id = os.environ.get('OSS_ACCESS_KEY_ID') access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET') role_arn_for_oss_upload = os.environ.get('OSS_STS_ROLE_ARN') # 自定義會話名稱 role_session_name = 'role_session_name' # 指定過期時間,單位為秒 expire_time = 3600 bucket = 'examplebucket' region_id = 'cn-hangzhou' # 初始化配置,直接傳遞憑據 config = Config( region_id=region_id, access_key_id=access_key_id, access_key_secret=access_key_secret ) # 創建 STS 客戶端并獲取臨時憑證 sts_client = Sts20150401Client(config=config) assume_role_request = sts_20150401_models.AssumeRoleRequest( role_arn=role_arn_for_oss_upload, role_session_name=role_session_name ) response = sts_client.assume_role(assume_role_request) token_data = response.body.credentials.to_map() # 使用 STS 返回的臨時憑據 sts_accesskeyid = token_data['AccessKeyId'] sts_accesskeysecret = token_data['AccessKeySecret'] security_token = token_data['SecurityToken'] now = int(time.time()) # 將時間戳轉換為datetime對象 dt_obj = datetime.datetime.utcfromtimestamp(now) # 在當前時間增加3小時,設置為請求的過期時間 dt_obj_plus_3h = dt_obj + datetime.timedelta(hours=3) # 請求時間 dt_obj_1 = dt_obj.strftime('%Y%m%dT%H%M%S') + 'Z' # 請求日期 dt_obj_2 = dt_obj.strftime('%Y%m%d') # 請求過期時間 expiration_time = dt_obj_plus_3h.strftime('%Y-%m-%dT%H:%M:%S.000Z') # 步驟1:創建policy。 policy = { "expiration": expiration_time, "conditions": [ {"bucket": "cjl3"}, #請替換為目標Bucket名稱 {"x-oss-signature-version": "OSS4-HMAC-SHA256"}, {"x-oss-credential": f"{sts_accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request"}, {"x-oss-security-token": security_token}, {"x-oss-date": dt_obj_1}, ] } policy_str = json.dumps(policy).strip() # 步驟2:構造待簽名字符串(StringToSign) stringToSign = base64.b64encode(policy_str.encode()).decode() # 步驟3:計算SigningKey dateKey = hmacsha256(("aliyun_v4" + sts_accesskeysecret).encode(), dt_obj_2) dateRegionKey = hmacsha256(dateKey, "cn-hangzhou") dateRegionServiceKey = hmacsha256(dateRegionKey, "oss") signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request") # 步驟4:計算Signature result = hmacsha256(signingKey, stringToSign) signature = result.hex() return jsonify({ "policy": stringToSign, "x_oss_signature_version": "OSS4-HMAC-SHA256", "x_oss_credential": f"{sts_accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request", "x_oss_date": dt_obj_1, "signature": signature, "security_token": security_token }) if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)
步驟二:配置微信小程序
為了確保小程序向OSS發送文件的請求不會被微信攔截,在小程序平臺,使用Bucket域名配置微信小程序的合法域名。
在微信小程序端,使用從ECS服務端獲取到的V4簽名憑證信息,發送請求上傳文件到OSS。
小程序端上傳文件index.js文件示例代碼如下,完整示例工程請部署uploadoss .zip。
Page({ data: { key: 'filename.txt', //上傳文件名稱 policy: '', xOssSecurityToken: '', xOssSignatureVersion: '', xOssCredential: '', xOssDate: '', xOssSignature: '' }, //上傳文件方法 uploadFileToOSS(filePath, callback) { const { key, policy, xOssSecurityToken, xOssSignatureVersion, xOssCredential, xOssDate, xOssSignature } = this.data; const apiUrl='http://<ECS實例公網IP地址>:5000/generate_signature' //請將ip地址和端口替換為實際服務器公網ip地址及端口 // 發送請求獲取簽名信息 wx.request({ url: apiUrl, success: (res) => { this.data.xOssSignatureVersion = res.data.x_oss_signature_version; this.data.xOssCredential = res.data.x_oss_credential; this.data.xOssDate = res.data.x_oss_date; this.data.xOssSignature = res.data.signature; this.data.xOssSecurityToken = res.data.security_token; this.data.policy = res.data.policy; //上傳參數 const formData = { key, //上傳文件名稱 policy: this.data.policy, //表單域 'x-oss-signature-version': this.data.xOssSignatureVersion, //指定簽名的版本和算法 'x-oss-credential': this.data.xOssCredential, //指明派生密鑰的參數集 'x-oss-date': this.data.xOssDate, //請求的時間 'x-oss-signature': this.data.xOssSignature, //簽名認證描述信息 'x-oss-security-token': this.data.xOssSecurityToken, //安全令牌 success_action_status: "200" //上傳成功后響應狀態碼 }; // 發送請求上傳文件 wx.uploadFile({ url: 'https://*********.aliyuncs.com', // Bucket域名 請替換為目標Bucket域名 filePath: filePath, name: 'file', //固定值為file formData: formData, success(res) { console.log('上傳響應:', res); if (res.statusCode === 200) { callback(null, res.data); // 上傳成功 } else { console.error('上傳失敗,狀態碼:', res.statusCode); console.error('失敗響應:', res); callback(res); // 上傳失敗,返回響應 } }, fail(err) { console.error('上傳失敗:', err); // 輸出錯誤信息 wx.showToast({ title: '上傳失敗,請重試!', icon: 'none' }); callback(err); // 調用回調處理錯誤 } }); }, fail: (err) => { console.error('請求接口失敗:', err); wx.showToast({ title: '獲取上傳參數失敗,請重試!', icon: 'none' }); } }); }, //點擊上傳文件按鈕觸發上傳文件代碼邏輯 chooseAndUploadFile() { wx.chooseMessageFile({ count: 1, // 選擇一個文件 type: 'all', // 支持所有類型的文件 success: (res) => { console.log('選擇的文件:', res.tempFiles); // 輸出選擇的文件信息 if (res.tempFiles.length > 0) { const tempFilePath = res.tempFiles[0].path; // 獲取選擇的文件路徑 console.log('選擇的文件路徑:', tempFilePath); // 輸出文件路徑 this.uploadFileToOSS(tempFilePath, (error, data) => { if (error) { wx.showToast({ title: '上傳失敗!', icon: 'none' }); console.error('上傳失敗:', error); // 輸出具體的錯誤信息 } else { wx.showToast({ title: '上傳成功!', icon: 'success' }); console.log('上傳成功:', data); // 輸出上傳成功后的數據 } }); } else { wx.showToast({ title: '未選擇文件!', icon: 'none' }); } }, fail: (err) => { wx.showToast({ title: '選擇文件失敗!', icon: 'none' }); console.error('選擇文件失敗:', err); // 輸出選擇文件的錯誤信息 } }); } });
結果驗證
編譯運行后,在微信小程序界面單擊上傳文件。
在Bucket列表頁面,選擇上傳文件的Bucket并打開,單擊右上角 您可以在上傳列表中看到您通過小程序上傳的文件。