微信小程序可以将图片、文档、视频等文件上传到OSS,实现文件的云端存储和分发。
方案概览
微信小程序上传文件到OSS的过程如下:
要实现微信小程序上传文件到OSS,只需两步:
配置服务端:在ECS,创建一个实例,用于从STS服务获取一个临时访问凭证,然后使用临时访问凭证为微信小程序生成上传文件到OSS所需的凭证签名。
配置微信小程序:在小程序平台,使用Bucket域名配置微信小程序的合法域名,确保小程序向OSS发送文件的请求不会被微信拦截;在微信小程序端,实现从ECS获取签名并使用签名上传文件到OSS的功能。
操作步骤
步骤一:配置服务端
在实际部署时,如果您已经有自己的ECS服务器,则无需创建该ECS实例,可直接进行第2步ECS服务端计算签名。
创建并连接ECS实例。
ECS服务端计算签名。
重要服务端提供了以下两种方式获取STS临时访问凭证并计算签名。
ECS扮演RAM角色获取STS临时访问凭证计算签名:服务端不保留AK信息,采用ECS扮演RAM角色的方式,去访问STS服务以获取临时访问凭证并计算签名,最大程度上降低了AK信息泄露的风险,安全性较高。
RAM用户获取STS临时访问凭证计算签名:服务端需要保留AK信息,通过获取配置于服务端环境变量里的RAM用户AK信息,进而访问STS服务获取临时访问凭证并计算签名,安全性较低。
ECS扮演RAM角色获取STS临时访问凭证计算签名
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并打开,单击右上角 您可以在上传列表中看到您通过小程序上传的文件。