一、方案优势
·权限安全:可以有效限定安全的权限范围,只能用于上传指定的一个文件路径。
·路径安全:由服务端决定随机的 COS 文件路径,可以有效避免已有文件被覆盖的问题和安全风险。
二、上传流程
1. 客户端选择文件,将原始文件名发送给服务端。
2. 服务端根据文件名后缀,生成带时间的随机 COS 文件路径,并申请对应权限的临时密钥和 cos key 返回给前端。
3. iOS 端使用高级上传接口上传文件到 COS。
三、临时密钥搭建
临时密钥(临时访问凭证) 是通过 CAM 云 API 提供的接口,获取到权限受限的密钥。当需要发起 COS API 请求时,需要用到获取临时密钥接口返回信息中 的 TmpSecretId、TmpSecretKey 和 Token 三个字段,用于计算签名。
getKeyAndCredentials 接口会返回临时密钥信息和上传 需要的 Bucket、 Region、 key。
各语言示例代码:
NodeJSGoPHPPythonJava.Net完整代码可参考 示例代码。// 临时密钥服务例子const STS = require('qcloud-cos-sts');const express = require('express');const pathLib = require('path');// 配置参数const config = { secretId: process.env.SecretId, secretKey: process.env.SecretKey, proxy: process.env.Proxy, durationSeconds: 1800, bucket: process.env.Bucket, region: process.env.Region, // 密钥的上传操作权限列表 allowActions: [ // 简单上传 'name/cos:PutObject', // 分块上传 'name/cos:InitiateMultipartUpload', 'name/cos:ListMultipartUploads', 'name/cos:ListParts', 'name/cos:UploadPart', 'name/cos:CompleteMultipartUpload', ],};// 生成要上传的 COS 文件路径文件名const generateCosKey = function (ext) { const date = new Date(); const m = date.getMonth() + 1; const ymd = `${date.getFullYear()}${m < 10 ? `0${m}` : m}${date.getDate()}`; const r = ('000000' + Math.random() * 1000000).slice(-6); const cosKey = `file/${ymd}/${ymd}_${r}${ext ? `.${ext}` : ''}`; return cosKey;};// 创建临时密钥服务const app = express();app.use(function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); next();});// 获取临时密钥function getSts({ cosKey, condition }) { return new Promise((resolve, reject) => { // 获取临时密钥 const AppId = config.bucket.substr(config.bucket.lastIndexOf('-') + 1); let resource = 'qcs::cos:' + config.region + ':uid/' + AppId + ':' + config.bucket + '/' + cosKey; console.log('检查resource是否正确', resource); const policy = { version: '2.0', statement: [ { action: config.allowActions, effect: 'allow', resource: [ // cos 相关授权路径 resource, // ci 相关授权路径,按需使用 // 'qcs::ci:' + config.region + ':uid/' + AppId + ':bucket/' + config.bucket + '/' + 'job/*', ], condition }, ], }; const startTime = Math.round(Date.now() / 1000); STS.getCredential( { secretId: config.secretId, secretKey: config.secretKey, proxy: config.proxy, region: config.region, durationSeconds: config.durationSeconds, // endpoint: 'sts.internal.tencentcloudapi.com', // 支持设置 sts 内网域名 policy: policy, }, function (err, tempKeys) { if (tempKeys) tempKeys.startTime = startTime; if (err) { reject(err); } else { resolve(tempKeys); } } ); });}// 返回临时密钥和上传信息,客户端自行计算签名app.get('/getKeyAndCredentials', function (req, res, next) { // 业务自行实现 用户登录态校验,比如对 token 校验 // const userToken = req.query.userToken; // const canUpload = checkUserRole(userToken); // if (!canUpload) { // res.send({ error: '当前用户没有上传权限' }); // return; // } // 上传文件可控制类型、大小,按需开启 const permission = { limitExt: false, // 限制上传文件后缀 extWhiteList: ['jpg', 'jpeg', 'png', 'gif', 'bmp'], // 限制的上传后缀 limitContentType: false, // 限制上传 contentType limitContentLength: false, // 限制上传文件大小 }; // 客户端传进原始文件名,这里根据文件后缀生成随机 Key const filename = req.query.filename; if (!filename) { res.send({ error: '请传入文件名' }); return; } // pathLib.extname 返回带点的后缀(如 ".jpg"),去掉点再与白名单比较 const ext = pathLib.extname(filename).slice(1).toLowerCase(); const cosKey = generateCosKey(ext); const condition = {}; // 1. 限制上传文件后缀 if (permission.limitExt) { const extInvalid = !ext || !permission.extWhiteList.includes(ext); if (extInvalid) { res.send({ error: '非法文件,禁止上传' }); return; } } // 2. 限制上传文件 content-type if (permission.limitContentType) { Object.assign(condition, { 'string_like_if_exist': { // 只允许上传 content-type 为图片类型 'cos:content-type': 'image/*' } }); } // 3. 限制上传文件大小 if (permission.limitContentLength) { Object.assign(condition, { 'numeric_less_than_equal': { // 上传大小限制不能超过5MB(只对简单上传生效) 'cos:content-length': 5 * 1024 * 1024 }, }); } getSts({ cosKey, condition }) .then((data) => { res.send( Object.assign(data, { startTime: Math.round(Date.now() / 1000), bucket: config.bucket, region: config.region, key: cosKey, }) ); }) .catch((err) => { console.log('sts error', err); res.send(err); });});app.all('*', function (req, res, next) { res.send({ code: -1, message: '404 Not Found' });});// 启动签名服务app.listen(3000);console.log('app is listening at http://127.0.0.1:3000');完整代码可参考 示例代码。package mainimport ( "encoding/json" "fmt" "math/rand" "os" "reflect" "strings" "time" "unicode" sts "github.com/tencentyun/qcloud-cos-sts-sdk/go")type Config struct { filename string appId string SecretId string SecretKey string Proxy string DurationSeconds int Bucket string Region string AllowActions []string}type Permission struct { LimitExt bool `json:"limitExt"` ExtWhiteList []string `json:"extWhiteList"` LimitContentType bool `json:"limitContentType"` LimitContentLength bool `json:"limitContentLength"`}func generateCosKey(ext string) string { date := time.Now() ymd := fmt.Sprintf("%d%02d%d", date.Year(), int(date.Month()), date.Day()) r := fmt.Sprintf("%06d", rand.Intn(1000000)) cosKey := fmt.Sprintf("file/%s/%s_%s.%s", ymd, ymd, r, ext) return cosKey}func getPermission() Permission { permission := Permission{ LimitExt: false, ExtWhiteList: []string{"jpg", "jpeg", "png", "gif", "bmp"}, LimitContentType: false, LimitContentLength: false, } return permission}func getConfig() Config { config := Config{ filename: "test.jpg", appId: "12500000000", SecretId: os.Getenv("SECRETID"), // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140 SecretKey: os.Getenv("SECRETKEY"), // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140 Proxy: os.Getenv("Proxy"), DurationSeconds: 1800, Bucket: "bucket-12500000000", Region: "ap-guangzhou", AllowActions: []string{ "name/cos:PutObject", "name/cos:InitiateMultipartUpload", "name/cos:ListMultipartUploads", "name/cos:ListParts", "name/cos:UploadPart", "name/cos:CompleteMultipartUpload", }, } return config}func stringInSlice(str string, list []string) bool { for _, v := range list { if v == str { return true } } return false}func StructToCamelMap(input interface{}) map[string]interface{} { v := reflect.ValueOf(input) if v.Kind() == reflect.Ptr { v = v.Elem() } result := make(map[string]interface{}) typ := v.Type() for i := 0; i < v.NumField(); i++ { field := typ.Field(i) fieldValue := v.Field(i) // 转换字段名为小驼峰 key := toLowerCamel(field.Name) // 处理嵌套结构体 if fieldValue.Kind() == reflect.Struct || (fieldValue.Kind() == reflect.Ptr && fieldValue.Elem().Kind() == reflect.Struct) { // 注意:必须先判断 Kind 再调用 IsNil,否则对 struct 调用 IsNil 会 panic if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { result[key] = nil continue } result[key] = StructToCamelMap(fieldValue.Interface()) } else { // 处理基本类型 result[key] = fieldValue.Interface() } } return result}// 转换为小驼峰格式(首字母小写)func toLowerCamel(s string) string { if s == "" { return s } // 处理全大写单词(如 ID) if strings.ToUpper(s) == s { return strings.ToLower(s) } // 普通小驼峰转换 runes := []rune(s) runes[0] = unicode.ToLower(runes[0]) return string(runes)}func main() { config := getConfig() permission := getPermission() c := sts.NewClient( // 通过环境变量获取密钥, os.Getenv 方法表示获取环境变量 config.SecretId, // os.Getenv("SECRETID"), // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140 config.SecretKey, // os.Getenv("SECRETKEY"), // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140 nil, // sts.Host("sts.internal.tencentcloudapi.com"), // 设置域名, 默认域名 sts.tencentcloudapi.com // sts.Scheme("http"), // 设置协议, 默认为 https,公有云 sts 获取临时密钥不允许走 http,特殊场景才需要设置 http ) condition := make(map[string]map[string]interface{}) segments := strings.Split(config.filename, ".") ext := "" if len(segments) > 0 { ext = segments[len(segments)-1] } if permission.LimitExt { extInvalid := ext == "" || !stringInSlice(ext, permission.ExtWhiteList) if extInvalid { fmt.Printf("%+v\\n", "非法文件,禁止上传") return } } if permission.LimitContentType { condition["string_like_if_exist"] = map[string]interface{}{ // 只允许上传 content-type 为图片类型 "cos:content-type": "image/*", } } // 3. 限制上传文件大小 if permission.LimitContentLength { condition["numeric_less_than_equal"] = map[string]interface{}{ // 上传大小限制不能超过5MB(只对简单上传生效) "cos:content-length": 5 * 1024 * 1024, } } key := generateCosKey(ext) // 策略概述 https://cloud.tencent.com/document/product/436/18023 opt := &sts.CredentialOptions{ DurationSeconds: int64(config.DurationSeconds), Region: config.Region, Policy: &sts.CredentialPolicy{ Version: "2.0", Statement: []sts.CredentialPolicyStatement{ { // 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923 Action: config.AllowActions, Effect: "allow", Resource: []string{ // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用) // 存储桶的命名格式为 BucketName-APPID,此处填写的 bucket 必须为此格式 "qcs::cos:ap-guangzhou:uid/" + config.appId + ":" + config.Bucket + "/" + key, }, // 开始构建生效条件 condition // 关于 condition 的详细设置规则和 COS 支持的 condition 类型可以参考 https://cloud.tencent.com/document/product/436/71306 Condition: condition, }, }, }, } // case 1 请求临时密钥 res, err := c.GetCredential(opt) if err != nil { panic(err) } // 转换为小驼峰 map resultMap := StructToCamelMap(res) resultMap["bucket"] = config.Bucket resultMap["region"] = config.Region resultMap["key"] = key // 打印结果 jsonBytes, err := json.MarshalIndent(resultMap, "", " ") if err != nil { panic(err) } // 转换为字符串并打印 fmt.Println(string(jsonBytes))}完整代码可参考 示例代码。<?phprequire_once __DIR__ . '/vendor/autoload.php';use QCloud\\COSSTS\\Sts;// 生成要上传的 COS 文件路径文件名function generateCosKey($ext) { $ymd = date('Ymd'); $r = substr('000000' . rand(), -6); $cosKey = 'file/' . $ymd. '/' . $ymd . '_' . $r; if ($ext) { $cosKey = $cosKey . '.' . $ext; } return $cosKey;}// 获取单一文件上传权限的临时密钥function getKeyAndCredentials($filename) { // 业务自行实现 用户登录态校验,比如对 token 校验 // $canUpload = checkUserRole($userToken); // if (!$canUpload) { // return '当前用户没有上传权限'; // } // 上传文件可控制类型、大小,按需开启 $permission = array( 'limitExt' => false, // 限制上传文件后缀 'extWhiteList' => ['jpg', 'jpeg', 'png', 'gif', 'bmp'], // 限制的上传后缀 'limitContentType' => false, // 限制上传 contentType 'limitContentLength' => false, // 限制上传文件大小 ); $condition = array(); // 客户端传进原始文件名,这里根据文件后缀生成随机 Key $ext = pathinfo($filename, PATHINFO_EXTENSION); // 1. 限制上传文件后缀 if ($permission['limitExt']) { if ($ext === '' || !in_array($ext, $permission['extWhiteList'])) { return '非法文件,禁止上传'; } } // 2. 限制上传文件 content-type if ($permission['limitContentType']) { // 只允许上传 content-type 为图片类型 $condition['string_like_if_exist'] = array('cos:content-type' => 'image/*'); } // 3. 限制上传文件大小 if ($permission['limitContentLength']) { // 上传大小限制不能超过5MB(只对简单上传生效) $condition['numeric_less_than_equal'] = array('cos:content-length' => 5 * 1024 * 1024); } $cosKey = generateCosKey($ext); $bucket = 'test-131234567'; // 换成你的 bucket $region = 'ap-guangzhou'; // 换成 bucket 所在园区 $config = array( 'url' => 'https://sts.tencentcloudapi.com/', // url 和 domain保持一致 'domain' => 'sts.tencentcloudapi.com', // 域名,非必须,默认为 sts.tencentcloudapi.com 'proxy' => '', 'secretId' => getenv('GROUP_SECRET_ID'), // 固定密钥,若为明文密钥,请直接以'xxx'形式填入,不要填写到 getenv()函数中 'secretKey' => getenv('GROUP_SECRET_KEY'), // 固定密钥,若为明文密钥,请直接以'xxx'形式填入,不要填写到 getenv()函数中 'bucket' => $bucket, // 换成你的 bucket 'region' => $region, // 换成 bucket 所在园区 'durationSeconds' => 1800, // 密钥有效期 'allowPrefix' => array($cosKey), // 只分配当前 key 的路径权限 // 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923 'allowActions' => array ( // 简单上传 'name/cos:PutObject', // 分片上传 'name/cos:InitiateMultipartUpload', 'name/cos:ListMultipartUploads', 'name/cos:ListParts', 'name/cos:UploadPart', 'name/cos:CompleteMultipartUpload' ), ); if (!empty($condition)) { $config['condition'] = $condition; } $sts = new Sts(); $tempKeys = $sts->getTempKeys($config); $resTemp = array_merge( $tempKeys, [ 'startTime' => time(), 'bucket' => $bucket, 'region' => $region, 'key' => $cosKey, ] ); echo json_encode($resTemp, JSON_UNESCAPED_SLASHES); return $resTemp;}完整代码可参考 示例代码。#!/usr/bin/env python# coding=utf-8import jsonimport osimport datetimeimport randomfrom sts.sts import Stsif __name__ == '__main__': # 配置参数 config = { "filename":"test.jpg", "appId": "125000000", "secretId": os.getenv("SecretId"), "secretKey": os.getenv("SecretKey"), "proxy": os.getenv("Proxy"), "durationSeconds": 1800, "bucket": "bucket-125000000", "region": "ap-guangzhou", # 密钥的上传操作权限列表 "allowActions": [ # 简单上传 "name/cos:PutObject", # 分块上传 "name/cos:InitiateMultipartUpload", "name/cos:ListMultipartUploads", "name/cos:ListParts", "name/cos:UploadPart", "name/cos:CompleteMultipartUpload", ], } permission = { "limitExt": False, # 限制上传文件后缀 "extWhiteList": ["jpg", "jpeg", "png", "gif", "bmp"], # 限制的上传后缀 "limitContentType": False, # 限制上传 contentType "limitContentLength": False, # 限制上传文件大小 } # 生成要上传的 COS 文件路径文件名 def generate_cos_key(ext=None): date = datetime.datetime.now() ymd = date.strftime('%Y%m%d') r = str(int(random.random() * 1000000)).zfill(6) cos_key = f"file/{ymd}/{ymd}_{r}.{ext if ext else ''}" return cos_key segments = config['filename'].split(".") ext = segments[-1] if segments else "" key = generate_cos_key(ext) resource = f"qcs::cos:{config['region']}:uid/{config['appId']}:{config['bucket']}/{key}" condition = {} # 1. 限制上传文件后缀 if permission["limitExt"]: ext_invalid = not ext or ext not in permission["extWhiteList"] if ext_invalid: print('非法文件,禁止上传') # 2. 限制上传文件 content-type if permission["limitContentType"]: condition.update({ "string_like_if_exist": { # 只允许上传 content-type 为图片类型 "cos:content-type": "image/*" } }) # 3. 限制上传文件大小 if permission["limitContentLength"]: condition.update({ "numeric_less_than_equal": { # 上传大小限制不能超过5MB(只对简单上传生效) "cos:content-length": 5 * 1024 * 1024 } }) def get_credential_demo(): credentialOption = { # 临时密钥有效时长,单位是秒 'duration_seconds': config.get('durationSeconds'), 'secret_id': config.get("secretId"), # 固定密钥 'secret_key': config.get("secretKey"), # 换成你的 bucket 'bucket': config.get("bucket"), 'proxy': config.get("proxy"), # 换成 bucket 所在地区 'region': config.get("region"), "policy": { "version": '2.0', "statement": [ { "action": config.get("allowActions"), "effect": "allow", "resource": [ resource ], "condition": condition } ], }, } try: sts = Sts(credentialOption) response = sts.get_credential() credential_dic = dict(response) credential_info = credential_dic.get("credentials") credential = { "bucket": config.get("bucket"), "region": config.get("region"), "key": key, "startTime": credential_dic.get("startTime"), "expiredTime": credential_dic.get("expiredTime"), "requestId": credential_dic.get("requestId"), "expiration": credential_dic.get("expiration"), "credentials": { "tmpSecretId": credential_info.get("tmpSecretId"), "tmpSecretKey": credential_info.get("tmpSecretKey"), "sessionToken": credential_info.get("sessionToken"), }, } print('get data : ' + json.dumps(credential, indent=4)) except Exception as e: print(e) get_credential_demo()完整代码可参考 示例代码。package com.tencent.cloud;import com.tencent.cloud.assumerole.AssumeRoleParam;import com.tencent.cloud.cos.util.Jackson;import org.junit.Test;import java.io.File;import java.io.FileInputStream;import java.text.SimpleDateFormat;import java.util.*;public class GetKeyAndCredentialsTest { public static String generateCosKey(String ext) { Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); String ymd = dateFormat.format(date); Random random = new Random(); int r = random.nextInt(1000000); String rStr = String.format("%06d", r); String cosKey = String.format("file/%s/%s_%s.%s", ymd, ymd, rStr, ext != null ? ext : ""); return cosKey; } // 获取配置信息 public TreeMap<String,Object> getConfig(){ String bucket = "test-12500000000"; String appId = "12500000000"; String filename = "test.jpg"; String region = "ap-guangzhou"; String secretId = ""; String secretKey = ""; String proxy = ""; int durationSeconds = 1800; String[] segments = filename.split("\\\\."); String ext = segments.length > 0 ? segments[segments.length – 1] : ""; // 临时密钥限制 Boolean limitExt = false; // 限制上传文件后缀 List extWhiteList = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp"); // 限制的上传后缀 Boolean limitContentType = false; // 限制上传 contentType Boolean limitContentLength = false; // 限制上传文件大小 Map<String, Object> condition = new HashMap(); // 1. 限制上传文件后缀 if (limitExt) { boolean extInvalid = ext == null || !extWhiteList.contains(ext); if (extInvalid) { System.out.println("非法文件,禁止上传"); return null; } } // 2. 限制上传文件 content-type if (limitContentType) { condition.put("string_like_if_exist", new HashMap<String, String>() {{ put("cos:content-type", "image/*"); }}); } // 3. 限制上传文件大小(只对简单上传生效) if (limitContentLength) { condition.put("numeric_less_than_equal", new HashMap<String, Long>() {{ put("cos:content-length", 5L * 1024 * 1024); }}); } String key = generateCosKey(ext); String resource = "qcs::cos:" + region + ":uid/" + appId + ':' + bucket + '/' + key; List allowActions = Arrays.asList( // 简单上传 "name/cos:PutObject", // 分块上传 "name/cos:InitiateMultipartUpload", "name/cos:ListMultipartUploads", "name/cos:ListParts", "name/cos:UploadPart", "name/cos:CompleteMultipartUpload" ); // 构建policy Map<String, Object> policy = new HashMap(); policy.put("version", "2.0"); Map<String, Object> statement = new HashMap(); statement.put("action", allowActions); statement.put("effect", "allow"); List<String> resources = Arrays.asList( resource ); statement.put("resource", resources); statement.put("condition", condition); policy.put("statement", Arrays.asList(statement)); // 构建config TreeMap <String,Object> config = new TreeMap<String, Object>(); config.put("secretId",secretId); config.put("secretKey",secretKey); config.put("proxy",proxy); config.put("duration",durationSeconds); config.put("bucket",bucket); config.put("region",region); config.put("key",key); config.put("policy",Jackson.toJsonPrettyString(policy)); return config; } /** * 基本的临时密钥申请示例,适合对一个桶内的一批对象路径,统一授予一批操作权限 */ @Test public void testGetKeyAndCredentials() { TreeMap config = this.getConfig(); try { Response response = CosStsClient.getCredential(config); TreeMap <String,Object> credential = new TreeMap<String, Object>(); TreeMap <String,Object> credentials = new TreeMap<String, Object>(); credentials.put("tmpSecretId",response.credentials.tmpSecretId); credentials.put("tmpSecretKey",response.credentials.tmpSecretKey); credentials.put("sessionToken",response.credentials.sessionToken); credential.put("startTime",response.startTime); credential.put("expiredTime",response.expiredTime); credential.put("requestId",response.requestId); credential.put("expiration",response.expiration); credential.put("credentials",credentials); credential.put("bucket",config.get("bucket")); credential.put("region",config.get("region")); credential.put("key",config.get("key")); System.out.println(Jackson.toJsonPrettyString(credential)); } catch (Exception e) { e.printStackTrace(); throw new IllegalArgumentException("no valid secret !"); } }}完整代码可参考 示例代码。using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Reflection;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Net.Mail;using COSSTS;using Newtonsoft.Json;using Formatting = System.Xml.Formatting;namespace COSSnippet{ public class GetKeyAndCredentials { //永久密钥 string secretId = ""; string secretKey = ""; string bucket = "bucket-125000000"; string appId = "125000000"; string region = "ap-guangzhou"; string filename = "test.jpg"; int time = 1800; // 限制 Boolean limitExt = false; // 限制上传文件后缀 List<string> extWhiteList = new List<String> { "jpg", "jpeg", "png", "gif", "bmp" }; // 限制的上传后缀 Boolean limitContentType = false; // 限制上传 contentType Boolean limitContentLength = false; // 限制上传文件大小 public string generateCosKey(string ext) { DateTime date = DateTime.Now; int m = date.Month; string ymd = $"{date.Year}{(m < 10 ? $"0{m}" : m.ToString())}{date.Day}"; Random random = new Random(); string r = random.Next(0, 1000000).ToString("D6"); // 生成6位随机数,前面补零 string cosKey = $"file/{ymd}/{ymd}_{r}.{(string.IsNullOrEmpty(ext) ? "" : ext)}"; return cosKey; } public Dictionary<string, object> getConfig() { Dictionary<string, object> config = new Dictionary<string, object>(); string[] allowActions = new string[] { // 允许的操作范围,这里以上传操作为例 "name/cos:PutObject", "name/cos:InitiateMultipartUpload", "name/cos:ListMultipartUploads", "name/cos:ListParts", "name/cos:UploadPart", "name/cos:CompleteMultipartUpload", }; string[] segments = filename.Split("."); string ext = segments.Length > 0 ? segments[segments.Length – 1] : string.Empty; string key = generateCosKey(ext); string resource = $"qcs::cos:{region}:uid/{appId}:{bucket}/{key}"; var condition = new Dictionary<string, object>(); // 1. 限制上传文件后缀 if (limitExt) { var extInvalid = string.IsNullOrEmpty(ext) || !extWhiteList.Contains(ext); if (extInvalid) { Console.WriteLine("非法文件,禁止上传"); return null; } } // 2. 限制上传文件 content-type if (limitContentType) { condition["string_like_if_exist"] = new Dictionary<string, string> { { "cos:content-type", "image/*" } // 只允许上传 content-type 为图片类型 }; } // 3. 限制上传文件大小(只对简单上传生效) if (limitContentLength) { condition["numeric_less_than_equal"] = new Dictionary<string, long> { { "cos:content-length", 5 * 1024 * 1024 } // 上传大小限制不能超过5MB }; } var policy = new Dictionary<string, object> { { "version", "2.0" }, { "statement", new List<Dictionary<string, object>> { new Dictionary<string, object> { { "action", allowActions }, { "effect", "allow" }, { "resource", new List<string> { resource, } }, { "condition", condition } } } } }; // 序列化为 JSON 并输出 string jsonPolicy = JsonConvert.SerializeObject(policy); config.Add("bucket", bucket); config.Add("region", region); config.Add("durationSeconds", time); config.Add("secretId", secretId); config.Add("secretKey", secretKey); config.Add("cosKey", key); config.Add("policy", jsonPolicy); return config; } // 获取联合身份临时访问凭证 https://cloud.tencent.com/document/product/1312/48195 public Dictionary<string, object> GetCredential() { var config = getConfig(); //获取临时密钥 Dictionary<string, object> credential = STSClient.genCredential(config); Dictionary<string, object> credentials = JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject((object) credential["Credentials"])); Dictionary<string, object> credentials1 = new Dictionary<string, object>(); credentials1.Add("tmpSecretId",credentials["TmpSecretId"]); credentials1.Add("tmpSecretKey",credentials["TmpSecretKey"]); credentials1.Add("sessionToken",credentials["Token"]); Dictionary<string, object> dictionary1 = new Dictionary<string, object>(); dictionary1.Add("credentials",credentials1); dictionary1.Add("startTime",credential["StartTime"]); dictionary1.Add("requestId",credential["RequestId"]); dictionary1.Add("expiration",credential["Expiration"]); dictionary1.Add("expiredTime",credential["ExpiredTime"]); dictionary1.Add("bucket",config["bucket"]); dictionary1.Add("region",config["region"]); dictionary1.Add("key",config["key"]); return dictionary1; } static void Main(string[] args) { GetKeyAndCredentials m = new GetKeyAndCredentials(); Dictionary<string, object> result = m.GetCredential(); string Credentials = JsonConvert.SerializeObject(result); Console.WriteLine($"{Credentials}"); } }}
四、客户端发起上传
详细使用步骤请查看 快速入门。
– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 一、初始化 COS SDK ,初始化一次即可,建议放在 app 已启动 就进行。 QCloudServiceConfiguration *configuration = [QCloudServiceConfiguration new]; configuration.appID = @"1250000000"; QCloudCOSXMLEndPoint *endpoint = [[QCloudCOSXMLEndPoint alloc] init]; endpoint.regionName = @"ap-guangzhou"; configuration.endpoint = endpoint; [QCloudCOSXMLService registerDefaultCOSXMLWithConfiguration:configuration]; [QCloudCOSTransferMangerService registerDefaultCOSTransferMangerWithConfiguration:configuration]; } // 二、调用业务服务 sts 接口,获取临时密钥 – (void)signatureWithFields:(QCloudSignatureFields *)fileds request:(QCloudBizHTTPRequest *)request urlRequest:(NSMutableURLRequest *)urlRequst compelete:(QCloudHTTPAuthentationContinueBlock)continueBlock { __block NSDictionary * credentialsInfo; [[[NSURLSession sharedSession]dataTaskWithURL:[NSURL URLWithString:@"https://******"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // 从业务服务接口请求临时密钥。 NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingTopLevelDictionaryAssumed error:nil]; credentialsInfo = dic; NSDictionary * credentials = dic[@"credentials"]; QCloudCredential *credential = [QCloudCredential new]; credential.secretID = credentials[@"tmpSecretId"]; credential.secretKey = credentials[@"tmpSecretKey"]; credential.token = credentials[@"sessionToken"]; //签名过期时间 credential.expirationDate = [NSDate dateWithTimeIntervalSince1970:[dic[@"expiredTime"] integerValue]]; credential.startDate = [NSDate dateWithTimeIntervalSince1970:[dic[@"startTime"] integerValue]];; QCloudAuthentationV5Creator *creator = [[QCloudAuthentationV5Creator alloc] initWithCredential:credential]; QCloudSignature *signature = [creator signatureForData:urlRequst]; continueBlock(signature, nil); }] resume]; } // 三、使用 QCloudCOSXMLUploadObjectRequest 开始上传。 -(void)upload { QCloudCOSXMLUploadObjectRequest* put = [QCloudCOSXMLUploadObjectRequest new]; // 从业务接口请求到的临时凭证 QCloudCredential *credential = [QCloudCredential new]; credential.secretID = @"临时密钥 SecretId"; credential.secretKey = @"临时密钥 SecretKey"; credential.token = @"临时密钥 Token"; // 将请求到的临时密钥设置给 QCloudCOSXMLUploadObjectRequest 实例对象。 put.credential = credential; /** 本地文件路径,请确保 URL 是以 file:// 开头,格式如下 : 1. [NSURL URLWithString:@"file:///var/mobile/Containers/Data/Application/DBPF7490-D5U8-4ABF-A0AF-CC49D6A60AEB/Documents/exampleobject"] 2. [NSURL fileURLWithPath:@"/var/mobile/Containers/Data/Application/DBPF7490-D5U8-4ABF-A0AF-CC49D6A60AEB/Documents/exampleobject"] */ NSURL* url = [NSURL fileURLWithPath:@"文件的URL"]; // 存储桶名称,由BucketName-Appid 组成,可以在 COS 控制台查看 https://console.cloud.tencent.com/cos5/bucket put.bucket = credentialsInfo[@"bucket"]; // 对象键,是对象在 COS 上的完整路径,如果带目录的话,格式为 "video/xxx/movie.mp4" put.object = credentialsInfo[@"key"]; put.regionName = credentialsInfo[@"region"]; // 需要上传的对象内容。可以传入 NSData*或者 NSURL*类型的变量 put.body = url; // 监听上传进度 [put setSendProcessBlock:^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) { // bytesSent 本次要发送的字节数(一个大文件可能要分多次发送) // totalBytesSent 已发送的字节数 // totalBytesExpectedToSend 本次上传要发送的总字节数(即一个文件大小) }]; // 监听上传结果 [put setFinishBlock:^(QCloudUploadObjectResult *result, NSError *error) { // 在上传结果 result.location 中获取上传文件的下载链接 NSString * fileUrl = result.location; // 获取文件 crc64 NSString * crc64 = [result.__originHTTPURLResponse__.allHeaderFields valueForKey:@"x-cos-hash-crc64ecma"]; }]; [put setInitMultipleUploadFinishBlock:^(QCloudInitiateMultipartUploadResult * multipleUploadInitResult, QCloudCOSXMLUploadObjectResumeData resumeData) { // 在初始化分块上传完成以后会回调该 block,在这里可以获取 resumeData,uploadid NSString* uploadId = multipleUploadInitResult.uploadId; }]; [[QCloudCOSTransferMangerService defaultCOSTransferManager] UploadObject:put];}
客户端完整代码可参考 示例代码。
五、相关文档
·上传报错问题可参考 iOS SDK 常见问题。
·如何安全上传对象可参考 上传安全限制。