本文档介绍如何搭建安全的临时签名服务。
方案优势
权限安全:可以有效限定安全的权限范围,只能用于上传指定的一个文件路径。
路径安全:由服务端决定随机的 COS 文件路径,可以有效避免已有文件被覆盖的问题和安全风险。
传输安全:在服务端生成签名,避免临时密钥传输过程中泄漏的风险。
上传流程
1. 客户端选择文件,客户端将原始文件名发送给服务端。
2. 服务端根据文件名后缀,生成带时间的随机 COS 文件路径,并申请对应权限的签名信息和 cos key 返回给前端。
临时签名服务搭建
临时密钥(临时访问凭证)是通过 CAM 云 API 提供的接口,获取到权限受限的密钥。当需要发起 COS API 请求时,需要用到获取临时密钥接口返回信息中 的 TmpSecretId、TmpSecretKey 和 Token 三个字段,用于计算签名。
在请求服务端签名时,会先获取临时密钥,然后用临时密钥生成签名,返回客户端。
以下示例各语言示例代码:
// 临时密钥服务例子var express = require('express');var crypto = require('crypto');var moment = require('moment');var STS = require('qcloud-cos-sts');const url = require('url');const { log } = require('console');// 配置参数var config = {// 获取腾讯云密钥,建议使用限定权限的子用户的密钥 https://console.cloud.tencent.com/cam/capisecretId: process.env.COS_SECRET_ID,secretKey: process.env.COS_SECRET_KEY,// 密钥有效期durationSeconds: 1800,// 这里填写存储桶、地域,例如:test-1250000000、ap-guangzhoubucket: process.env.PERSIST_BUCKET,region: process.env.PERSIST_BUCKET_REGION};// 获取临时密钥var getTempCredential = async function(cosKey){var shortBucketName = config.bucket.substr(0 , config.bucket.lastIndexOf('-'));var appId = config.bucket.substr(1 + config.bucket.lastIndexOf('-'));// 开始获取临时密钥var policy = {"version": "2.0","statement": [{"action": [// 上传需要的操作"name/cos:PutObject"],"effect": "allow","resource": [// 仅限cosKey资源'qcs::cos:' + config.region + ':uid/' + appId + ':prefix//' + appId + '/' + shortBucketName + '/' + cosKey,]}]};let tempKeys = null;try{tempKeys = await STS.getCredential({secretId: config.secretId,secretKey: config.secretKey,durationSeconds: config.durationSeconds,policy: policy,});console.log(tempKeys);return tempKeys;} catch(err){console.log(err);res.send(JSON.stringify(err));return null;}};// 计算签名var getSignature = function(tempCredential, httpMethod, cosHost, pathname) {const signAlgorithm = 'sha1';const credentials = tempCredential.credentials;const keyTime = `${tempCredential.startTime};${tempCredential.expiredTime}`;// 步骤一:生成 SignKeyvar signKey = crypto.createHmac(signAlgorithm, credentials.tmpSecretKey).update(keyTime).digest('hex');console.log("signKey:"+signKey);// 步骤二:生成 StringToSignconst httpString = `${httpMethod.toLowerCase()}\\n/${pathname}\\n\\nhost=${cosHost}\\n`;console.log("httpString:"+httpString);const httpStringHash = crypto.createHash(signAlgorithm).update(httpString).digest('hex');const stringToSign = `${signAlgorithm}\\n${keyTime}\\n${httpStringHash}\\n`;console.log("stringToSign:"+stringToSign);// 步骤三:生成 Signaturevar signature = crypto.createHmac(signAlgorithm, signKey).update(stringToSign).digest('hex');console.log("signature:"+signature);// 步骤四:生成 authorizationlet authorization = `q-sign-algorithm=${signAlgorithm}&q-ak=${credentials.tmpSecretId}&q-sign-time=${keyTime}&q-key-time=${keyTime}&q-header-list=host&q-url-param-list=&q-signature=${signature}`;// 去掉掉上面换行导致的\\nauthorization = authorization.replace(/\\n/g, '');console.log("authorization:"+authorization);return authorization;}// 创建临时密钥服务和用于调试的静态服务var app = express();// 直传签名接口app.all('/sts-server-sign', async function (req, res, next) {// 获取需要签名的字段var httpMethod = req.query.httpMethod;var cosHost = req.query.host;var cosKey = req.query.cosKey;console.log(httpMethod + " " + cosHost + " " + cosKey);cosHost = decodeURIComponent(cosHost);cosKey = decodeURIComponent(cosKey);console.log(httpMethod + " " + cosHost + " " + cosKey);// 判断异常情况if (!config.secretId || !config.secretKey) return res.send({ code: '-1', message: 'secretId or secretKey not ready' });if (!config.bucket || !config.region) return res.send({ code: '-1', message: 'bucket or regions not ready' });if (!httpMethod || !cosHost || !cosKey) return res.send({ code: '-1', message: 'httpMethod or host or coskey is not empty' });// 开始获取临时密钥var tempCredential = await getTempCredential(cosKey);if(!tempCredential){res.send({ code: -1, message: 'get temp credentials fail' });return;}// 用临时密钥计算签名let authorization = getSignature(tempCredential, httpMethod, cosHost, cosKey);// 返回域名、文件路径、签名、凭证信息res.send({code: 0,data: {cosHost: cosHost,cosKey: cosKey,authorization: authorization,securityToken: tempCredential.credentials.sessionToken},});});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 ("context""encoding/json""errors""fmt""github.com/tencentyun/cos-go-sdk-v5"sts "github.com/tencentyun/qcloud-cos-sts-sdk""math/rand""net/http""net/url""os""reflect""strings""time""unicode")type Config struct {filename stringappId stringSecretId stringSecretKey stringProxy stringDurationSeconds intBucket stringRegion stringAllowActions []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()m := int(date.Month()) + 1ymd := fmt.Sprintf("%d%02d%d", date.Year(), m, 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: true,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/37140SecretKey: os.Getenv("SECRETKEY"), // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140Proxy: os.Getenv("Proxy"),DurationSeconds: 1800,Bucket: "0-1253960454",Region: "ap-guangzhou",AllowActions: []string{"name/cos:PutObject"},}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) {if fieldValue.IsNil() && fieldValue.Kind() == reflect.Ptr {result[key] = nilcontinue}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 getStsCredential() (map[string]interface{}, error) {config := getConfig()permission := getPermission()c := sts.NewClient(// 通过环境变量获取密钥, os.Getenv 方法表示获取环境变量config.SecretId, //os.Getenv("SECRETID"), // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140config.SecretKey, //os.Getenv("SECRETKEY"), // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140nil,// 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, ".")if len(segments) == 0 {//ext := ""}ext := segments[len(segments)-1]if permission.LimitExt {extInvalid := ext == "" || !stringInSlice(ext, permission.ExtWhiteList)if extInvalid {return nil, errors.New("非法文件,禁止上传")}}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/18023opt := &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/31923Action: 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/71306Condition: condition,},},},}// case 1 请求临时密钥res, err := c.GetCredential(opt)if err != nil {return nil, err}// 转换为小驼峰 mapresultMap := StructToCamelMap(res)resultMap["bucket"] = config.BucketresultMap["region"] = config.RegionresultMap["key"] = keyreturn resultMap, nil}func main() {result, err := getStsCredential()if err != nil {fmt.Printf("请求临时密钥失败: %v\\n", err)return // 根据上下文决定是否退出}// 类型断言 credentials 为 map[string]interface{}credentials, ok := result["credentials"].(map[string]interface{})if !ok {fmt.Println("凭证格式错误")return}// 带类型检查的字段获取tak, tok := credentials["tmpSecretID"].(string)tsk, tskok := credentials["tmpSecretKey"].(string)token, tokenok := credentials["sessionToken"].(string)if !tok || !tskok || !tokenok {fmt.Println("临时凭证字段缺失或类型错误")return}host := "https://" + result["bucket"].(string) + ".cos." + result["region"].(string) + ".myqcloud.com"u, _ := url.Parse(host)b := &cos.BaseURL{BucketURL: u}c := cos.NewClient(b, &http.Client{})name := result["key"].(string)ctx := context.Background()opt := &cos.PresignedURLOptions{Query: &url.Values{},Header: &http.Header{},}opt.Query.Add("x-cos-security-token", token)signature := c.Object.GetSignature(ctx, http.MethodPut, name, "tak", "tsk", time.Hour, opt, true)fmt.Printf("%s%s%s%s", tak, tsk, signature, token)data := make(map[string]string)// 添加token和signdata["securityToken"] = tokendata["authorization"] = signaturedata["cosHost"] = hostdata["cosKey"] = name// 转换为JSONjsonData, err := json.MarshalIndent(data, "", " ")if err != nil {fmt.Println("JSON编码失败:", err)return}fmt.Println(string(jsonData))}
<?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 === '' || array_key_exists($ext, $permission['extWhiteList'])) {return '非法文件,禁止上传';}}// 2. 限制上传文件 content-typeif ($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-125000000'; // 换成你的 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'),);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,]);return $resTemp;}function getSign(){$filename = "test.jpg";$method = "putObject";$result = getKeyAndCredentials($filename);$credentials = $result["credentials"];$sessionToken = $credentials["sessionToken"];$tmpSecretId = $credentials["tmpSecretId"];$tmpSecretKey = $credentials["tmpSecretKey"];$expiredTime = $result["expiredTime"];$startTime = $result["startTime"];$bucket = $result["bucket"];$region = $result["region"];$key = $result["key"];$cosClient = new Qcloud\\Cos\\Client(array('region' => $region,'scheme' => 'https', //协议头部,默认为 http'signHost' => true, //默认签入Header Host;您也可以选择不签入Header Host,但可能导致请求失败或安全漏洞,若不签入host则填false'credentials'=> array('secretId' => $tmpSecretId,'secretKey' => $tmpSecretKey,'token' => $sessionToken)));### 简单上传预签名try {$url = $cosClient->getPresignedUrl($method,array('Bucket' => $bucket,'Key' => $key,'Body' => "",'Params'=> array('x-cos-security-token' => $sessionToken),'Headers'=> array(),), $expiredTime - $startTime); //签名的有效时间$parsedUrl = parse_url($url);$host = 'https://' . $parsedUrl['host']; // 自动包含端口(如果有)$queryString = isset($parsedUrl['query']) ? $parsedUrl['query'] : '';$queryParts = explode('&', $queryString);$signParts = array_filter($queryParts, function($part) {return strpos($part, 'x-cos-security-token=') !== 0;});$sign = implode('&', $signParts);$result = ['cosHost' => $host,'cosKey' => $key,'authorization' => $sign,'securityToken' => $sessionToken];echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);} catch (\\Exception $e) {// 请求失败echo($e);}}getSign();
#!/usr/bin/env python# coding=utf-8import jsonimport osimport datetimeimport randomfrom urllib.parse import urlencodefrom qcloud_cos import CosConfig, CosS3Clientfrom sts.sts import Stsif __name__ == '__main__':# 配置参数config = {"filename":"test.jpg","appId": "1250000000","secretId": os.getenv("SecretId"),"secretKey": os.getenv("SecretKey"),"proxy": os.getenv("Proxy"),"durationSeconds": 1800,"bucket": "0-1253960454","region": "ap-guangzhou",# 密钥的上传操作权限列表"allowActions": [# 简单上传"name/cos:PutObject"],}permission = {"limitExt": True, # 限制上传文件后缀"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_keysegments = 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-typeif 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"),},}return credentialexcept Exception as e:print(e)result = get_credential_demo()credentials = result["credentials"]secret_id = credentials["tmpSecretId"]secret_key = credentials["tmpSecretKey"]token = credentials["sessionToken"]bucket = result["bucket"]region = result["region"]key = result["key"]expired = result["expiredTime"]config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token)client = CosS3Client(config)sign = client.get_auth(Method='put',Bucket=bucket, Key=key, Expired=expired, Params={'x-cos-security-token': token},SignHost=True)sign = urlencode(dict([item.split('=', 1) for item in sign.split('&')]))host = "https://" + result["bucket"] + ".cos." + result["region"] + ".myqcloud.com"response = {"cosHost":host,"cosKey":key,"authorization":sign,"securityToken":token}print('get data : ' + json.dumps(response, indent=4))
package com.tencent.cloud;import com.qcloud.cos.COSClient;import com.qcloud.cos.ClientConfig;import com.qcloud.cos.auth.BasicSessionCredentials;import com.qcloud.cos.auth.COSCredentials;import com.qcloud.cos.http.HttpMethodName;import com.qcloud.cos.region.Region;import com.tencent.cloud.cos.util.Jackson;import org.junit.Test;import java.net.URL;import java.text.SimpleDateFormat;import java.util.*;public class ServerSignTest {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 = "0-1250000000";String appId = "1250000000";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; // 限制上传 contentTypeBoolean 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-typeif (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");// 构建policyMap<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));// 构建configTreeMap <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;}public TreeMap <String,Object> getKeyAndCredentials() {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"));return credential;} catch (Exception e) {e.printStackTrace();throw new IllegalArgumentException("no valid secret !");}}/*** 基本的临时密钥申请示例,适合对一个桶内的一批对象路径,统一授予一批操作权限*/@Testpublic void testGetKeyAndCredentials() {TreeMap <String,Object> credential = this.getKeyAndCredentials();TreeMap <String,Object> credentials = (TreeMap<String, Object>) credential.get("credentials");try {String tmpSecretId = (String) credentials.get("tmpSecretId");String tmpSecretKey = (String) credentials.get("tmpSecretKey");String sessionToken = (String) credentials.get("sessionToken");Date expiredTime = new Date((Long) credential.get("expiredTime"));String key = (String) credential.get("key");String bucket = (String) credential.get("bucket");String region = (String) credential.get("region");COSCredentials cred = new BasicSessionCredentials(tmpSecretId, tmpSecretKey, sessionToken);ClientConfig clientConfig = new ClientConfig();clientConfig.setRegion(new Region(region));COSClient cosClient = new COSClient(cred, clientConfig);Map<String, String> headers = new HashMap<String,String>();Map<String, String> params = new HashMap<String,String>();params.put("x-cos-security-token",sessionToken);URL url = cosClient.generatePresignedUrl(bucket, key, expiredTime, HttpMethodName.PUT, headers, params);String host = "https://" + url.getHost();String query = url.toString().split("\\\\?")[1];String sign = query.split("&x-cos-security-token")[0];TreeMap <String,Object> result = new TreeMap<String, Object>();result.put("cosHost",host);result.put("cosKey",key);result.put("authorization",sign);result.put("securityToken",sessionToken);System.out.println(Jackson.toJsonPrettyString(result));} catch (Exception e) {e.printStackTrace();throw new IllegalArgumentException("no valid sign !");}}}
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 COSXML;using COSXML.Auth;using COSXML.Model.Tag;using Newtonsoft.Json;using Formatting = System.Xml.Formatting;namespace COSSnippet{public class ServerSign{//永久密钥string secretId = "";string secretKey = "";string bucket = "bucket-1250000000";string appId = "1250000000";string region = "ap-guangzhou";string filename = "test.jpg";string method = "put";int time = 1800;// 限制Boolean limitExt = false; // 限制上传文件后缀List<string> extWhiteList = new List<String> { "jpg", "jpeg", "png", "gif", "bmp" }; // 限制的上传后缀Boolean limitContentType = false; // 限制上传 contentTypeBoolean 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"};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-typeif (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("key", key);config.Add("policy", jsonPolicy);return config;}// 获取联合身份临时访问凭证 https://cloud.tencent.com/document/product/1312/48195public 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){ServerSign m = new ServerSign();Dictionary<string, object> result = m.GetCredential();Dictionary<string, object> credentials = (Dictionary<string, object>)result["credentials"];string tmpSecretId = (string)credentials["tmpSecretId"];string tmpSecretKey = (string)credentials["tmpSecretKey"];string sessionToken = (string)credentials["sessionToken"];string bucket = (string)result["bucket"];string region = (string)result["region"];string key = (string)result["key"];long expiredTime = (long)result["expiredTime"];int startTime = (int)result["startTime"];QCloudCredentialProvider cosCredentialProvider = new DefaultSessionQCloudCredentialProvider(tmpSecretId, tmpSecretKey, expiredTime, sessionToken);CosXmlConfig config = new CosXmlConfig.Builder().IsHttps(true) //设置默认 HTTPS 请求.SetRegion(region) //设置一个默认的存储桶地域.SetDebugLog(true) //显示日志.Build(); //创建 CosXmlConfig 对象CosXml cosXml = new CosXmlServer(config, cosCredentialProvider);PreSignatureStruct preSignatureStruct = new PreSignatureStruct();preSignatureStruct.appid = m.appId;//"1250000000";preSignatureStruct.region = region;//"COS_REGION";preSignatureStruct.bucket = bucket;//"examplebucket-1250000000";preSignatureStruct.key = "exampleObject"; //对象键preSignatureStruct.httpMethod = "PUT"; //HTTP 请求方法preSignatureStruct.isHttps = true; //生成 HTTPS 请求 URLpreSignatureStruct.signDurationSecond = 600; //请求签名时间为 600spreSignatureStruct.headers = null; //签名中需要校验的 headerpreSignatureStruct.queryParameters = null; //签名中需要校验的 URL 中请求参数//上传预签名 URL (使用永久密钥方式计算的签名 URL)Dictionary<string, string> queryParameters = new Dictionary<string, string>();queryParameters.Add("x-cos-security-token",sessionToken);Dictionary<string, string> headers = new Dictionary<string, string>();string authorization = cosXml.GenerateSign(m.method,key,queryParameters,headers,expiredTime - startTime,expiredTime - startTime);string host = "https://" + bucket + ".cos." + region + ".myqcloud.com";Dictionary<string, object> response = new Dictionary<string, object>();response.Add("cosHost",host);response.Add("cosKey",key);response.Add("authorization",authorization);response.Add("securityToken",sessionToken);Console.WriteLine($"{JsonConvert.SerializeObject(response)}");}}}