微信小程序可以将图片、文档、视频等文件上传到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信息,以及所扮演RAM角色的ARN。进而访问STS服务获取临时访问凭证并计算签名,安全性较低。
ECS扮演RAM角色获取STS临时访问凭证计算签名RAM用户获取STS临时访问凭证计算签名ECS是阿里云提供的云服务器,下述代码示例需在云服务器环境中运行,本地环境不支持此类操作。
使用ECS时,无需创建RAM用户,只需将RAM角色授予ECS实例,便可通过扮演该角色获取STS临时访问凭证并计算签名。
ECS绑定RAM角色。
服务端计算签名。
JavaPythonNode.jsGoPHP请参考以下示例完成Java服务端的V4签名计算。完整示例工程请部署upload_server.zip。请注意,此示例工程基于JDK 23和Spring Boot 3.4.0构建,您参考时请根据自身环境合理调整,确保代码正常运行。
配置依赖。
<!-- 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服务端的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服务端的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}`); });
请参考以下示例完成Go服务端的V4签名计算。
配置依赖。
go get -u github.com/aliyun/credentials-go go mod tidy
代码示例。
package main import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "github.com/aliyun/credentials-go/credentials" "hash" "io" "log" "net/http" "os" "time" ) // 定义全局变量 var ( region string bucketName string product = "oss" ) // PolicyToken 结构体用于存储生成的表单数据 type PolicyToken struct { Policy string `json:"policy"` SecurityToken string `json:"security_token"` SignatureVersion string `json:"x_oss_signature_version"` Credential string `json:"x_oss_credential"` Date string `json:"x_oss_date"` Signature string `json:"signature"` } func main() { // 定义默认的IP和端口字符串 strIPPort := ":5000" if len(os.Args) == 3 { strIPPort = fmt.Sprintf("%s:%s", os.Args[1], os.Args[2]) } else if len(os.Args) != 1 { fmt.Println("Usage : go run callbackserver.go ") fmt.Println("Usage : go run callbackserver.go ip port ") fmt.Println("Example : go run callbackserver.go 11.22.**.** 80 ") fmt.Println("Example : go run callbackserver.go 0.0.0.0 5000 ") fmt.Println("") os.Exit(0) } // 打印服务器运行的地址和端口 fmt.Printf("server is running on %s \n", strIPPort) // 注册处理根路径请求的函数 http.HandleFunc("/", handlerRequest) // 启动HTTP服务器 err := http.ListenAndServe(strIPPort, nil) if err != nil { strError := fmt.Sprintf("http.ListenAndServe failed : %s \n", err.Error()) panic(strError) } } // handlerRequest 函数处理HTTP请求 func handlerRequest(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { response := get_policy_token() w.Header().Set("Access-Control-Allow-Methods", "POST") // 设置允许的源为所有(存在安全风险,生产环境需谨慎使用) w.Header().Set("Access-Control-Allow-Origin", "*") io.WriteString(w, response) } } func get_policy_token() string { // 设置bucket所处地域 region = "cn-hangzhou" // 设置bucket名称 bucketName = "bucketName" // 创建阿里云凭证配置 config := new(credentials.Config). // 填写Credential类型,固定值为ram_role_arn SetType("ecs_ram_role"). // 填写 ECS 实例所扮演的 RAM 角色名称 SetRoleName("roleName"). // 可选参数,禁用 IMDSv1,建议开启 SetDisableIMDSv1(true) // 根据配置创建凭证提供器 provider, err := credentials.NewCredential(config) if err != nil { log.Fatalf("NewCredential fail, err:%v", err) } // 从凭证提供器获取凭证 cred, err := provider.GetCredential() if err != nil { log.Fatalf("GetCredential fail, err:%v", err) } // 构建policy utcTime := time.Now().UTC() date := utcTime.Format("20060102") // 设置签名过期时间为当前时间起一个小时后过期 expiration := utcTime.Add(1 * time.Hour) // 示例policy表单域只列举了部分必填字段,如有其他需求可参考签名版本4文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend policyMap := map[string]any{ "expiration": expiration.Format("2006-01-02T15:04:05.000Z"), "conditions": []any{ map[string]string{"bucket": bucketName}, map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"}, map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",*cred.AccessKeyId, date, region, product)}, map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")}, map[string]string{"x-oss-security-token": *cred.SecurityToken}, }, } // 将policy转换为 JSON 格式 policy, err := json.Marshal(policyMap) if err != nil { log.Fatalf("json.Marshal fail, err:%v", err) } // 构造待签名字符串(StringToSign) stringToSign := base64.StdEncoding.EncodeToString([]byte(policy)) hmacHash := func() hash.Hash { return sha256.New() } // 构建signing key signingKey := "aliyun_v4" + *cred.AccessKeySecret h1 := hmac.New(hmacHash, []byte(signingKey)) io.WriteString(h1, date) h1Key := h1.Sum(nil) h2 := hmac.New(hmacHash, h1Key) io.WriteString(h2, region) h2Key := h2.Sum(nil) h3 := hmac.New(hmacHash, h2Key) io.WriteString(h3, product) h3Key := h3.Sum(nil) h4 := hmac.New(hmacHash, h3Key) io.WriteString(h4, "aliyun_v4_request") h4Key := h4.Sum(nil) // 生成签名 h := hmac.New(hmacHash, h4Key) io.WriteString(h, stringToSign) signature := hex.EncodeToString(h.Sum(nil)) // 构建返回给前端的表单 policyToken := PolicyToken{ Policy: stringToSign, Credential: fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",*cred.AccessKeyId, date, region, product), SignatureVersion: "OSS4-HMAC-SHA256", Signature: signature, Date: utcTime.UTC().Format("20060102T150405Z"), SecurityToken: *cred.SecurityToken, } response, err := json.Marshal(policyToken) if err != nil { fmt.Println("json err:", err) } fmt.Println(string(response)) // 返回表单 return string(response) }
请参考以下示例完成PHP服务端的V4签名计算。
配置依赖。
composer require alibabacloud/credentials
代码示例。
<?php require_once 'vendor/autoload.php'; use AlibabaCloud\Credentials\Credential; // 设置bucket所处地域 $region = 'cn-hangzhou'; // 设置bucket名称 $bucket = 'bucketName'; $product = 'oss'; // 创建阿里云凭证配置 $config = new Credential\Config([ // 填写Credential类型,固定值为ecs_ram_role。 'type' => 'ecs_ram_role', // 设置 ECS 实例的 RAM 角色名称 'roleName' => "roleName", ]); // 根据配置创建凭证对象 $credential = new Credential($config); // 从凭证对象中获取凭证信息 $cred = $credential->getCredential(); // 获取当前的 UTC 时间 $utcTime = new DateTime('now', new DateTimeZone('UTC')); // 格式化当前日期为 Ymd 格式,例如 20240101 $date = $utcTime->format('Ymd'); // 克隆当前时间对象,用于设置过期时间 $expiration = clone $utcTime; // 设置过期时间为当前时间往后 1 小时 $expiration->add(new DateInterval('PT1H')); // 构建policy // 示例policy表单域只列举了部分必填字段,如有其他需求可参考签名版本4文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend $policyMap = [ "expiration" => $expiration->format('Y-m-d\TH:i:s.000\Z'), "conditions" => [ ["bucket" => $bucket], ["x-oss-signature-version" => "OSS4-HMAC-SHA256"], ["x-oss-credential" => sprintf("%s/%s/%s/%s/aliyun_v4_request",$cred->getAccessKeyId(), $date, $region, $product)], ["x-oss-date" => $utcTime->format('Ymd\THis\Z')], ["x-oss-security-token" => $cred->getSecurityToken()], ], ]; // 将policy转换为 JSON 格式的字符串 $policy = json_encode($policyMap); // 对policy字符串进行 Base64 编码,得到待签名的字符串 $stringToSign = base64_encode($policy); // 构建signingKey,由固定字符串 "aliyun_v4" 和访问密钥 Secret 拼接而成 $signingKey = "aliyun_v4" . $cred->getAccessKeySecret(); $h1Key = hmacSign($signingKey, $date); $h2Key = hmacSign($h1Key, $region); $h3Key = hmacSign($h2Key, $product); $h4Key = hmacSign($h3Key, "aliyun_v4_request"); // 使用 h4Key 对待签名的字符串进行 HMAC-SHA256 签名,得到最终的签名 $signature = hash_hmac('sha256', $stringToSign, $h4Key); // 构建响应给前端的表单数据,包含policy、签名版本、凭证、日期、签名和安全令牌等信息 echo json_encode(array( 'policy' => $stringToSign, "x_oss_signature_version" => "OSS4-HMAC-SHA256", "x_oss_credential" => sprintf("%s/%s/%s/%s/aliyun_v4_request", $cred->getAccessKeyId(), $date, $region, $product), "x_oss_date" => $utcTime->format('Ymd\THis\Z'), "signature" => $signature, "security_token" => $cred->getSecurityToken() )); // hmacSign 函数,用于进行 HMAC-SHA256 签名计算 function hmacSign($key, $data) { return hash_hmac('sha256', $data, $key, true); }
RAM用户授权。有关如何创建RAM用户,请参见创建RAM用户。
RAM角色授权。有关如何创建RAM角色,请参见创建可信实体为阿里云账号的RAM角色。
服务端计算签名。
JavaPythonNode.jsGoPHP请参考以下示例完成Java服务端的V4签名计算。完整示例工程请部署upload_server.zip。请注意,此示例工程基于JDK 23和Spring Boot 3.4.0构建,您参考时请根据自身环境合理调整,确保代码正常运行。
配置环境变量。
<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
请分别替换为RAM用户的AccessKey ID、AccessKeySecret。如何创建AccessKey ID和AccessKeySecret请参见创建AccessKey。<ROLE_ARN>
请替换为目标角色ARN,在角色页面单击目标RAM角色名称,然后在基本信息区域查看对应的ARN。
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>
配置依赖。
<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 { /** * 使用环境变量中的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服务端的V4签名计算。
配置环境变量。
<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
请分别替换为RAM用户的AccessKey ID、AccessKeySecret。如何创建AccessKey ID和AccessKeySecret请参见创建AccessKey。<ROLE_ARN>
请替换为目标角色ARN,在角色页面单击目标RAM角色名称,然后在基本信息区域查看对应的ARN。
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>
配置依赖。
pip install oss2 pip install alibabacloud_tea_openapi alibabacloud_sts20150401 alibabacloud_credentials
代码示例。
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服务端的V4签名计算。
配置环境变量。
<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
请分别替换为RAM用户的AccessKey ID、AccessKeySecret。如何创建AccessKey ID和AccessKeySecret请参见创建AccessKey。<ROLE_ARN>
请替换为目标角色ARN,在角色页面单击目标RAM角色名称,然后在基本信息区域查看对应的ARN。
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>
配置依赖。
npm install ali-oss npm install @alicloud/credentials npm install express
代码示例。
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}`); });
请参考以下示例完成Go服务端的V4签名计算。
配置环境变量。
<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
请分别替换为RAM用户的AccessKey ID、AccessKeySecret。如何创建AccessKey ID和AccessKeySecret请参见创建AccessKey。<ROLE_ARN>
请替换为目标角色ARN,在角色页面单击目标RAM角色名称,然后在基本信息区域查看对应的ARN。
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>
配置依赖。
go get -u github.com/aliyun/credentials-go go mod tidy
代码示例。
package main import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "github.com/aliyun/credentials-go/credentials" "hash" "io" "log" "net/http" "os" "time" ) // 定义全局变量 var ( region string bucketName string product = "oss" ) // PolicyToken 结构体用于存储生成的表单数据 type PolicyToken struct { Policy string `json:"policy"` SecurityToken string `json:"security_token"` SignatureVersion string `json:"x_oss_signature_version"` Credential string `json:"x_oss_credential"` Date string `json:"x_oss_date"` Signature string `json:"signature"` } func main() { // 定义默认的IP和端口字符串 strIPPort := ":8080" if len(os.Args) == 3 { strIPPort = fmt.Sprintf("%s:%s", os.Args[1], os.Args[2]) } else if len(os.Args) != 1 { fmt.Println("Usage : go run callbackserver.go ") fmt.Println("Usage : go run callbackserver.go ip port ") fmt.Println("Example : go run callbackserver.go 11.22.**.** 80 ") fmt.Println("Example : go run callbackserver.go 0.0.0.0 8080 ") fmt.Println("") os.Exit(0) } // 打印服务器运行的地址和端口 fmt.Printf("server is running on %s \n", strIPPort) // 注册处理根路径请求的函数 http.HandleFunc("/", handlerRequest) // 启动HTTP服务器 err := http.ListenAndServe(strIPPort, nil) if err != nil { strError := fmt.Sprintf("http.ListenAndServe failed : %s \n", err.Error()) panic(strError) } } // handlerRequest 函数处理HTTP请求 func handlerRequest(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { response := get_policy_token() w.Header().Set("Access-Control-Allow-Methods", "POST") // 设置允许的源为所有(存在安全风险,生产环境需谨慎使用) w.Header().Set("Access-Control-Allow-Origin", "*") io.WriteString(w, response) } } func get_policy_token() string { // 设置bucket所处地域 region = "cn-hangzhou" // 设置bucket名称 bucketName = "bucketName" config := new(credentials.Config). // 填写Credential类型,固定值为ram_role_arn。 SetType("ram_role_arn"). // 从环境变量中获取RAM用户的访问密钥(AccessKeyId和AccessKeySecret)。 SetAccessKeyId(os.Getenv("ALIBABA_ACCESS_KEY_ID")). SetAccessKeySecret(os.Getenv("ALIBABA_ACCESS_KEY_SECRET")). // 从环境变量中获取RAM角色的ARN信息,即需要扮演的角色ID。格式为acs:ram::$accountID:role/$roleName。 SetRoleArn(os.Getenv("ROLE_ARN")). // 自定义角色会话名称,用于区分不同的令牌。 SetRoleSessionName("Role_Session_Name"). // (可选)限制STS Token权限。 SetPolicy(""). // (可选)限制STS Token的有效时间。 SetRoleSessionExpiration(3600) // 根据配置创建凭证提供器 provider, err := credentials.NewCredential(config) if err != nil { log.Fatalf("NewCredential fail, err:%v", err) } // 从凭证提供器获取凭证 cred, err := provider.GetCredential() if err != nil { log.Fatalf("GetCredential fail, err:%v", err) } // 构建policy utcTime := time.Now().UTC() date := utcTime.Format("20060102") // 设置签名过期时间为当前时间起一个小时后过期 expiration := utcTime.Add(1 * time.Hour) // 示例policy表单域只列举了部分必填字段,如有其他需求可参考文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend policyMap := map[string]any{ "expiration": expiration.Format("2006-01-02T15:04:05.000Z"), "conditions": []any{ map[string]string{"bucket": bucketName}, map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"}, map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request",*cred.AccessKeyId, date, region, product)}, map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")}, map[string]string{"x-oss-security-token": *cred.SecurityToken}, }, } // 将policy转换为 JSON 格式 policy, err := json.Marshal(policyMap) if err != nil { log.Fatalf("json.Marshal fail, err:%v", err) } // 构造待签名字符串(StringToSign) stringToSign := base64.StdEncoding.EncodeToString([]byte(policy)) hmacHash := func() hash.Hash { return sha256.New() } // 构建signing key signingKey := "aliyun_v4" + *cred.AccessKeySecret h1 := hmac.New(hmacHash, []byte(signingKey)) io.WriteString(h1, date) h1Key := h1.Sum(nil) h2 := hmac.New(hmacHash, h1Key) io.WriteString(h2, region) h2Key := h2.Sum(nil) h3 := hmac.New(hmacHash, h2Key) io.WriteString(h3, product) h3Key := h3.Sum(nil) h4 := hmac.New(hmacHash, h3Key) io.WriteString(h4, "aliyun_v4_request") h4Key := h4.Sum(nil) // 生成签名 h := hmac.New(hmacHash, h4Key) io.WriteString(h, stringToSign) signature := hex.EncodeToString(h.Sum(nil)) // 构建返回给前端的表单 policyToken := PolicyToken{ Policy: stringToSign, Credential: fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request", *cred.AccessKeyId, date, region, product), SignatureVersion: "OSS4-HMAC-SHA256", Signature: signature, Date: utcTime.UTC().Format("20060102T150405Z"), SecurityToken: *cred.SecurityToken, } response, err := json.Marshal(policyToken) if err != nil { fmt.Println("json err:", err) } fmt.Println(string(response)) // 返回表单 return string(response) }
请参考以下示例完成PHP服务端的V4签名计算。
配置环境变量。
<ALIBABA_CLOUD_ACCESS_KEY_ID>
、<ALIBABA_CLOUD_ACCESS_KEY_SECRET>
请分别替换为RAM用户的AccessKey ID、AccessKeySecret。如何创建AccessKey ID和AccessKeySecret请参见创建AccessKey。<ROLE_ARN>
请替换为目标角色ARN,在角色页面单击目标RAM角色名称,然后在基本信息区域查看对应的ARN。
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>
配置依赖。
composer require alibabacloud/credentials
代码示例。
<?php require_once 'vendor/autoload.php'; use AlibabaCloud\Credentials\Credential; // 设置响应头为JSON格式 header('Content-Type: application/json'); try { // 设置bucket所处地域 $region = 'cn-hangzhou'; // 设置bucket名称 $bucket = 'bucketName'; $product = 'oss'; // 从环境变量获取凭证 $accessKeyId = getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'); $accessKeySecret = getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'); $roleArn = getenv('ROLE_ARN'); if (!$accessKeyId || !$accessKeySecret || !$roleArn) { throw new Exception('环境变量未正确配置'); } // 创建阿里云凭证配置 $config = new Credential\Config([ 'type' => 'ram_role_arn', 'accessKeyId' => $accessKeyId, 'accessKeySecret' => $accessKeySecret, 'roleArn' => $roleArn, 'roleSessionName' => 'role_session_name', 'policy' => '', // 设置临时访问凭证过期时间为3600秒 'roleSessionExpiration' => 3600, ]); // 根据配置创建凭证对象 $credential = new Credential($config); // 从凭证对象中获取凭证信息 $cred = $credential->getCredential(); // 获取当前的 UTC 时间 $utcTime = new DateTime('now', new DateTimeZone('UTC')); // 格式化当前日期为 Ymd 格式,例如 20240101 $date = $utcTime->format('Ymd'); // 克隆当前时间对象,用于设置过期时间 $expiration = clone $utcTime; // 设置过期时间为当前时间往后 1 小时 $expiration->add(new DateInterval('PT1H')); // 构建policy // 示例policy表单域只列举了部分必填字段,如有其他需求可参考文档:https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend $policyMap = [ "expiration" => $expiration->format('Y-m-d\TH:i:s.000\Z'), "conditions" => [ ["bucket" => $bucket], ["x-oss-signature-version" => "OSS4-HMAC-SHA256"], ["x-oss-credential" => sprintf("%s/%s/%s/%s/aliyun_v4_request", $cred->getAccessKeyId(), $date, $region, $product)], ["x-oss-date" => $utcTime->format('Ymd\THis\Z')], ["x-oss-security-token" => $cred->getSecurityToken()], ], ]; // 将policy转换为 JSON 格式的字符串 $policy = json_encode($policyMap); // 对policy字符串进行 Base64 编码,得到待签名的字符串 $stringToSign = base64_encode($policy); // 构建signingKey,由固定字符串 "aliyun_v4" 和访问密钥 Secret 拼接而成 $signingKey = "aliyun_v4" . $cred->getAccessKeySecret(); $h1Key = hash_hmac('sha256', $date, $signingKey, true); $h2Key = hash_hmac('sha256', $region, $h1Key, true); $h3Key = hash_hmac('sha256', $product, $h2Key, true); $h4Key = hash_hmac('sha256', 'aliyun_v4_request', $h3Key, true); // 使用 h4Key 对待签名的字符串进行 HMAC-SHA256 签名,得到最终的签名 $signature = hash_hmac('sha256', $stringToSign, $h4Key); // 构建响应给前端的表单数据,包含policy、签名版本、凭证、日期、签名和安全令牌等信息 $responseData = [ 'policy' => $stringToSign, "x_oss_signature_version" => "OSS4-HMAC-SHA256", "x_oss_credential" => sprintf("%s/%s/%s/%s/aliyun_v4_request", $cred->getAccessKeyId(), $date, $region, $product), "x_oss_date" => $utcTime->format('Ymd\THis\Z'), "signature" => $signature, "security_token" => $cred->getSecurityToken() ]; echo json_encode($responseData); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); }
步骤二:配置微信小程序
为了确保小程序向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并打开,单击右上角 您可以在上传列表中看到您通过小程序上传的文件。
- 本页导读 (1)
- 方案概览
- 操作步骤
- 步骤一:配置服务端
- 步骤二:配置微信小程序
- 结果验证