微信小程序可以将图片、文档、视频等文件上传到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是阿里云提供的云服务器,下述代码示例需在云服务器环境中运行,本地环境不支持此类操作。
使用ECS时,无需创建RAM用户,只需将RAM角色授予ECS实例,便可通过扮演该角色获取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"); //请替换为步骤a(ECS绑定RAM角色)中,ECS所绑定的RAM角色名称 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 // 示例policy表单域只列举必填字段,如有其他需求可参考文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend 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"); //请将<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"); //请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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"); //请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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"); //请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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', # 请替换为步骤a(ECS绑定RAM角色)中,ECS所绑定的RAM角色名称 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表单域只列举必填字段,如有其他需求可参考文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend policy = { "expiration": expiration_time, "conditions": [ {"bucket": "bucket_name"}, #请将<bucket_name>替换为您的实际Bucket名称 {"x-oss-signature-version": "OSS4-HMAC-SHA256"}, {"x-oss-credential": f"{accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request"}, #请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing {"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") #请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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", #指明派生密钥的参数集,请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing "x_oss_date": dt_obj_1, #请求的时间 "signature": signature, #签名认证描述信息 "security_token": security_token #安全令牌 }) if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)
Node.js
请参考以下示例完成Node.js服务端的V4签名计算。
配置依赖。
npm install ali-oss npm install @alicloud/credentials npm install express
代码示例。
const express = require('express'); const OSS = require('ali-oss'); const Credential = require('@alicloud/credentials'); const { getCredential } = require('ali-oss/lib/common/signUtils'); const { getStandardRegion } = require('ali-oss/lib/common/utils/getStandardRegion'); const { policy2Str } = require('ali-oss/lib/common/utils/policy2Str'); const app = express(); const PORT = process.env.PORT || 5000; //服务请求端口号 const ECSGenerateSignature = async () => { //初始化ECS RAM角色凭证 const credentialsConfig = new Credential.Config({ type: 'ecs_ram_role', // 固定值无需更改 roleName: 'role_name', // 请替换为ECS扮演的角色名称 }); const credentialClient = new Credential.default(credentialsConfig); const { accessKeyId, accessKeySecret, securityToken } = await credentialClient.getCredential(); // 初始化OSS客户端 const client = new OSS({ bucket: 'bucketname', // 请替换为目标Bucket名称 region: 'cn-hangzhou', // 请替换为标Bucket所在地域 accessKeyId, accessKeySecret, stsToken: securityToken, refreshSTSTokenInterval: 0, refreshSTSToken: async () => { const { accessKeyId, accessKeySecret, securityToken } = await credentialClient.getCredential(); return { accessKeyId, accessKeySecret, stsToken: securityToken }; }, }); // 创建表单数据Map const formData = new Map(); // 设置签名过期时间为当前时间往后推10分钟 const date = new Date(); const expirationDate = new Date(date); expirationDate.setMinutes(date.getMinutes() + 10); // 格式化日期为符合ISO 8601标准的UTC时间字符串格式 function padTo2Digits(num) { return num.toString().padStart(2, '0'); } function formatDateToUTC(date) { return ( date.getUTCFullYear() + padTo2Digits(date.getUTCMonth() + 1) + padTo2Digits(date.getUTCDate()) + 'T' + padTo2Digits(date.getUTCHours()) + padTo2Digits(date.getUTCMinutes()) + padTo2Digits(date.getUTCSeconds()) + 'Z' ); } const formattedDate = formatDateToUTC(expirationDate); // 生成x-oss-credential并设置表单数据 const credential = getCredential(formattedDate.split('T')[0], getStandardRegion(client.options.region), client.options.accessKeyId); formData.set('x_oss_date', formattedDate); formData.set('x_oss_credential', credential); formData.set('x_oss_signature_version', 'OSS4-HMAC-SHA256'); // 创建policy // 示例policy表单域只列举必填字段,如有其他需求可参考签名版本4文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend const policy = { expiration: expirationDate.toISOString(), conditions: [ { 'bucket':"bucketname"}, // "bucketname"请替换为目标bucket名称 { 'x-oss-credential': credential }, { 'x-oss-signature-version': 'OSS4-HMAC-SHA256' }, { 'x-oss-date': formattedDate }, ], }; // 如果存在STS Token,添加到策略和表单数据中 if (client.options.stsToken) { policy.conditions.push({ 'x-oss-security-token': client.options.stsToken }); formData.set('security_token', client.options.stsToken); } // 生成签名并设置表单数据 const signature = client.signPostObjectPolicyV4(policy, date); formData.set('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64')); formData.set('signature', signature); // 返回表单数据 return Object.fromEntries(formData); }; app.get('/generate_signature', async (req, res) => { try { const result = await ECSGenerateSignature(); res.json(result); // 返回生成的签名数据 } catch (error) { console.error('Error generating signature:', error); res.status(500).send('Error generating signature'); } }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
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 // 示例policy表单域只列举必填字段,如有其他需求可参考文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend 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"); //请将<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"); //请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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"); //请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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"); //请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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表单域只列举必填字段,如有其他需求可参考签名版本4文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend policy = { "expiration": expiration_time, "conditions": [ {"bucket": "bucket_name"}, #请将<bucket_name>替换为您的实际Bucket名称 {"x-oss-signature-version": "OSS4-HMAC-SHA256"}, {"x-oss-credential": f"{sts_accesskeyid}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request"}, #请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing {"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") #请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing 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", #请将<cn-hangzhou>替换为您的实际Bucket所处地域,例如北京地域为:cn-beijing "x_oss_date": dt_obj_1, "signature": signature, "security_token": security_token }) if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)
Node.js
请参考以下示例完成Node.js服务端的V4签名计算。
配置依赖。
npm install ali-oss npm install @alicloud/credentials npm install express
配置环境变量。
说明<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>
代码示例。
const express = require('express'); const OSS = require('ali-oss'); const { STS } = require('ali-oss'); const { getCredential } = require('ali-oss/lib/common/signUtils'); const { getStandardRegion } = require('ali-oss/lib/common/utils/getStandardRegion'); const { policy2Str } = require('ali-oss/lib/common/utils/policy2Str'); const app = express(); const PORT = process.env.PORT || 5000; //服务请求端口号 const ECSGenerateSignature = async () => { // 初始化STS客户端 let sts = new STS({ accessKeyId : process.env.ALIBABA_CLOUD_ACCESS_KEY_ID, // 从环境变量中获取RAM用户的AccessKey ID accessKeySecret : process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET // 从环境变量中获取RAM用户的AccessKey Secret }); // 调用assumeRole接口获取STS临时访问凭证 const result = await sts.assumeRole(process.env.ROLE_ARN, '', '3600', 'sessiontest'); // 从环境变量中获取RAM角色ARN,并设置临时访问凭证有效期为3600秒,角色会话名称为sessiontest可自定义 // 提取临时访问凭证中的AccessKeyId、AccessKeySecret和SecurityToken const accessKeyId = result.credentials.AccessKeyId; const accessKeySecret = result.credentials.AccessKeySecret; const securityToken = result.credentials.SecurityToken; // 初始化OSS Client const client = new OSS({ bucket: 'bucketname', // 请替换为目标Bucket名称 region: 'cn-hangzhou', // 请替换为标Bucket所在地域 accessKeyId, accessKeySecret, stsToken: securityToken, refreshSTSTokenInterval: 0, refreshSTSToken: async () => { const { accessKeyId, accessKeySecret, securityToken } = await credentialClient.getCredential(); return { accessKeyId, accessKeySecret, stsToken: securityToken }; }, }); // 创建表单数据Map const formData = new Map(); // 设置签名过期时间为当前时间往后推10分钟 const date = new Date(); const expirationDate = new Date(date); expirationDate.setMinutes(date.getMinutes() + 10); // 格式化日期为符合ISO 8601标准的UTC时间字符串格式 function padTo2Digits(num) { return num.toString().padStart(2, '0'); } function formatDateToUTC(date) { return ( date.getUTCFullYear() + padTo2Digits(date.getUTCMonth() + 1) + padTo2Digits(date.getUTCDate()) + 'T' + padTo2Digits(date.getUTCHours()) + padTo2Digits(date.getUTCMinutes()) + padTo2Digits(date.getUTCSeconds()) + 'Z' ); } const formattedDate = formatDateToUTC(expirationDate); // 生成x-oss-credential并设置表单数据 const credential = getCredential(formattedDate.split('T')[0], getStandardRegion(client.options.region), client.options.accessKeyId); formData.set('x_oss_date', formattedDate); formData.set('x_oss_credential', credential); formData.set('x_oss_signature_version', 'OSS4-HMAC-SHA256'); // 创建policy // 示例policy表单域只列举必填字段,如有其他需求可参考文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend const policy = { expiration: expirationDate.toISOString(), conditions: [ { 'bucket':"bucketname"}, // "bucketname"请替换为目标bucket名称 { 'x-oss-credential': credential }, { 'x-oss-signature-version': 'OSS4-HMAC-SHA256' }, { 'x-oss-date': formattedDate }, ], }; // 如果存在STS Token,添加到策略和表单数据中 if (client.options.stsToken) { policy.conditions.push({ 'x-oss-security-token': client.options.stsToken }); formData.set('security_token', client.options.stsToken); } // 生成签名并设置表单数据 const signature = client.signPostObjectPolicyV4(policy, date); formData.set('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64')); formData.set('signature', signature); // 返回表单数据 return Object.fromEntries(formData); }; app.get('/generate_signature', async (req, res) => { try { const result = await ECSGenerateSignature(); res.json(result); // 返回生成的签名数据 } catch (error) { console.error('Error generating signature:', error); res.status(500).send('Error generating signature'); } }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
步骤二:配置微信小程序
为了确保小程序向OSS发送文件的请求不会被微信拦截,在小程序平台,使用Bucket域名配置微信小程序的合法域名。
在微信小程序端,使用从ECS服务端获取到的V4签名凭证信息,发送请求上传文件到OSS。
小程序端上传文件index.js文件示例代码如下,完整示例工程请部署uploadoss.zip。
Page({ data: { key: 'filename.txt', //待上传的文件名称,您也可以指定其存储在某个目录下。例如,将filename.txt文件上传到youfolder文件夹下,此时需填写:/youfolder/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地址>:<port>/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; //此示例上传参数只列举必填字段,如有其他需求可参考: //PostObject文档:https://help.aliyun.com/zh/oss/developer-reference/postobject //签名版本4文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend 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://examplebucket.oss-cn-hangzhou.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并打开,单击右上角 您可以在上传列表中看到您通过小程序上传的文件。