前端直传cos之使用临时密钥实现node获取临时密钥接口

news/2024/12/23 4:26:18/

背景

原来使用的cos是调用的node接口,但是由于公司node项目的网关限制了上传文件大小,然后的然后就由前端直传cos了(主要是还是自己动手丰衣足食);
但是呢!前端直传cos使用固定密钥是非常不安全的,所以使用node封装一个返回临时密钥的接口,然后前端调用临时密钥再上传cos~

具体实现

1. 实现node接口

  • 使用插件requestcrypto
  • 服务端使用固定密钥调用 STS 服务申请临时密钥(具体内容请参见文底参考文档)
  • STS服务接入(参考:https://github.com/tencentyun/qcloud-cos-sts-sdk/blob/master/nodejs/sdk/sts.js)
  • sts文件注意事项:
    • request引用使用报错,将引入改成import * as request from 'request' 即可;
    • 内部params参数按照sts参考文件都要加上,否则运行的时候会报错缺少参数;
    • action值是GetFederationTokenendpoint = 'sts.tencentcloudapi.com'就按照sts参考文件的来即可,不用再变了;
// sts.ts 文件
/* eslint-disable */ 
import * as request from 'request'
const crypto = require('crypto')
const StsUrl = 'https://{host}/'const util = {// 获取随机数getRandom(min, max) {return Math.round(Math.random() * (max - min) + min)},// obj 转 query stringjson2str(obj, $notEncode = '') {const arr: any = []Object.keys(obj).sort().forEach(item => {const val = obj[item] || ''arr.push(`${item}=${$notEncode ? encodeURIComponent(val) : val}`)})return arr.join('&')},// 计算签名getSignature(opt, key, method, stsDomain) {const formatString = `${method + stsDomain}/?${util.json2str(opt)}`const hmac = crypto.createHmac('sha1', key)const sign = hmac.update(Buffer.from(formatString, 'utf8')).digest('base64')return sign},// v2接口的key首字母小写,v3改成大写,此处做了向下兼容backwardCompat(data) {const compat:any = {}for (const key in data) {if (typeof data[key] === 'object') {compat[this.lowerFirstLetter(key)] = this.backwardCompat(data[key])} else if (key === 'Token') {compat.sessionToken = data[key]} else {compat[this.lowerFirstLetter(key)] = data[key]}}return compat},lowerFirstLetter(source) {return source.charAt(0).toLowerCase() + source.slice(1)},
}// 拼接获取临时密钥的参数
const getTempCredential = function (options, callback) {if (options?.durationInSeconds !== undefined) {console.warn('warning: durationInSeconds has been deprecated, Please use durationSeconds ).')}const secretId = options?.secretIdconst secretKey = options?.secretKeyconst proxy = options?.proxy || ''const region = options?.region || 'ap-beijing'const durationSeconds = options?.durationSeconds || options?.durationInSeconds || 1800const policy = options?.policyconst endpoint = 'sts.tencentcloudapi.com'const policyStr = JSON.stringify(policy)const action = options?.action || 'GetFederationToken'const nonce = util.getRandom(10000, 20000)const timestamp = parseInt(`${+new Date() / 1000}`) // eslint-disable-line no-undefconst method = 'POST'const name = 'cos-sts-nodejs' // 临时会话名称const params: any = {SecretId: secretId,Timestamp: timestamp,Nonce: nonce,Action: action,DurationSeconds: durationSeconds,Version: '2018-08-13',Region: region,Policy: encodeURIComponent(policyStr),}if (action === 'AssumeRole') {params.RoleSessionName = nameparams.RoleArn = options?.roleArn} else {params.Name = name}params.Signature = util.getSignature(params, secretKey, method, endpoint)const opt = {method,url: StsUrl.replace('{host}', endpoint),strictSSL: false,json: true,form: params,headers: {Host: endpoint,},proxy,}request(opt, (err, response, body) => {let data = body.Responseif (data) {if (data.Error) {callback(data.Error)} else {try {data.startTime = data.ExpiredTime - durationSecondsdata = util.backwardCompat(data)callback(null, data)} catch (e) {callback(new Error(`Parse Response Error: ${JSON.stringify(data)}`))}}} else {callback(err || body)}})
}// 获取联合身份临时访问凭证 GetFederationToken
const getCredential = (opt, callback) => {Object.assign(opt, { action: 'GetFederationToken' })if (callback) return getTempCredential(opt, callback)return new Promise((resolve, reject) => {getTempCredential(opt, (err, data) => {err ? reject(err) : resolve(data)})})
}}const STS = {getCredential,
}
export default STS
  • node调用sts接口调用固定密钥,生成临时密钥接口
    • 入参: 固定的密钥
    • 返回数据 临时token、临时密钥
async getTempCosKeyId() {const secretKeyId = await getSecretKeyId();// 配置参数const config = {secretId: secretKeyId.secretId, // 固定密钥secretKey: secretKeyId.secretKey, // 固定密钥proxy: "",host: "sts.tencentcloudapi.com", // 域名,非必须,默认为 sts.tencentcloudapi.comdurationSeconds: 1800, // 密钥有效期// 放行判断相关参数bucket: "bucket", // 换成你的 bucketregion: "region", // 换成 bucket 所在地区allowPrefix: "/web", // 上传文件前缀,可自定义为惯用前缀,};const bucket = config.bucket || "";const shortBucketName = bucket.slice(0, bucket.lastIndexOf("-"));const appId = bucket.slice(1 + bucket.lastIndexOf("-"));const policy = {version: "2.0",statement: [{action: [// 简单上传"name/cos:PutObject","name/cos:PostObject",// 分片上传"name/cos:InitiateMultipartUpload","name/cos:ListMultipartUploads","name/cos:ListParts","name/cos:UploadPart","name/cos:CompleteMultipartUpload",],effect: "allow",principal: { qcs: ["*"] },resource: [`qcs::cos:${config.region}:uid/${appId}:prefix//${appId}/${shortBucketName}/${config.allowPrefix}`,],},],};// 返回接口return new Promise((resolve, reject) => {STS.getCredential({secretId: config.secretId,secretKey: config.secretKey,proxy: config.proxy,policy,durationSeconds: config.durationSeconds,},(err, credential) => {if (!err) {resolve(credential);console.log(err || credential);} else {reject(err);}});}).catch((error) => error);}
  • 关于前缀配置,allowPrefix: "/web"上传文件前缀,可自定义为惯用前缀, 上传文件的时候必须用到否则接口403报错

2. 前端封装cos直传组件

  • 使用插件cos-js-sdk-v5
  • 将cos里面的密钥{SecretId:,SecretKey}换成获取临时密钥getAuthorization:(op,callback)=>{//接口获取临时密钥,执行callback}
  • 上传组件封装
    • 大于20m的进行分片上传cos.sliceUploadFile(),其他的就直接走上传cos.putObject()
import { UPLOAD_BUCKET, UPLOAD_REGION, UPLOAD_PREFIX, } from '@/utils/globalData';
import { queryGetTempCosKeyId } from '@/services/upload' // 获取临时密钥接口
const COS = require('cos-js-sdk-v5');
/*** @param  {object} option*/
export default function uploadCos(option) {const cos = new COS({// getAuthorization 必选参数getAuthorization: function (options, callback) {// 异步获取临时密钥queryGetTempCosKeyId().then(res => {const { credentials, expiredTime: ExpiredTime, startTime: StartTime } = res?.data?.resultcallback({TmpSecretId: credentials.tmpSecretId,TmpSecretKey: credentials.tmpSecretKey,SecurityToken: credentials.sessionToken,// 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误StartTime, // 时间戳,单位秒,如:1580000000ExpiredTime, // 时间戳,单位秒,如:1580000000});})}});const Bucket_Region_Config = {Bucket: UPLOAD_BUCKET,Region: UPLOAD_REGION,};const errFn = (err, data) => {if (err) {option.onError(err);} else {option.onSuccess(data);}}const progressFn = (progressData) => {if (!done) {option.onProgress(progressData);if (progressData.percent >= 1) {done = true;}}}const { file = {}, Prefix = UPLOAD_PREFIX } = option;let done = false;const timestamp = new Date().getTime();const newFileName = `${timestamp}_${file.name}`;if (file.size > 1024 * 1024 * 20) { // 大于20m走分片上传cos.sliceUploadFile({...Bucket_Region_Config,Key: (Prefix || '') + newFileName,Body: file,onProgress: (progressData) => progressFn(progressData)},(err, data) => errFn(err, data));} else {cos.putObject({...Bucket_Region_Config,Key: (Prefix || '') + newFileName,Body: file,onProgress: (progressData) => progressFn(progressData)},(err, data) => errFn(err, data));}return false;
}

3. 组件使用

  • 使用uploadCos方法上传文件,传入参数file,调用onSuccess成功方法获取文件,调用onError 方法获取失败原因
import uploadCos from '@/utils/uploadCos'
const uploadAction = (val) => {uploadCos({file: val.file,onSuccess: (completeData) => {setUploadVal(val => {console.log(`上传完成,文件为${completeData?.Location}`);},onError: (err) => {message.error(`文件上传失败,请稍后重试!,${err}`)},});
}

参数

node获取临时密钥接口:https://github.com/tencentyun/qcloud-cos-sts-sdk/blob/master/nodejs/demo/sts-server.js
前端使用临时密钥:https://cloud.tencent.com/document/product/436/11459
cos文档:https://cloud.tencent.com/document/product/436/14048


http://www.ppmy.cn/news/36961.html

相关文章

ChatGPT批量生成文章-ChatGPT文章生成器

ChatGPT:一键批量生成高质量文章,提高生产效率! 随着信息爆炸的时代,文本生产成为了各个行业必不可少的一部分。但面对高强度的生产需求,人力资源却难以跟上步伐。现在,我们有一款基于人工智能和自然语言处…

springcloudAlibaba---分布式事物组件Seata

事物简介 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据 库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。 这四个属性通常称为ACID特性。 原子性(atom…

【iOS】—— MRC

MRC 文章目录MRC野指针与空指针空指针野指针MRC避免循环引用自动释放池(AutoreleasePool)autorelease的使用方法autorelease 的注意事项自动释放池的嵌套使用autorelease 错误用法autorelease 实现逻辑在学习MRC之前要先看看iOS的内存五大分区&#xff0…

Nacos注册中心源码服务注册源码分析

前面一篇文章讲了客户端启动时候会去调用nacos服务端发起http请求进行服务注册,最终会调用到/nacos/v1/ns/instance这个接口上面,今天我们重点 来看服务是如何完成服务注册的,首先我们将nacos的源码下载下载,这里我下载的是1.4.1版…

vue尚品汇商城项目-day01【6.Footer组件的显示与隐藏】

文章目录6.Footer组件的显示与隐藏6.1我们可以根据组件身上的$route获取当前路由的信息,通过路由路径判断Footer显示与隐藏6.2配置路由的时候,可以给路由添加元信息[meta],路由需要配置对象,它的key不能乱接、瞎写、胡写&#xff…

Spring进阶

Spring 复习 1.什么是spring? spring是一个轻量级的(核心包),非侵入式的一站式(数据持久层,we层,核心,aop)的框架 为了简化企业级开发. 核心IOC,AOP IOC: 控制反转 把创建对象交给spring框架管理(创建对象与使用对象分离),管理对象的生命周期. DI: 依赖注入 创建对象后,把对象…

FRP内网穿透

FRP内网穿透 目录FRP内网穿透导读是什么安装与使用步骤内网与公网理解具体的配置原理与流程图总结导读 校园网内也就是局域网内部机器由于没有公网IP,因此无法被局域网外部的客户端直接访问。FRP就是这样一个用于内网穿透的应用,它需要一台拥有公网IP的…

Linux简单入门命令

1>帮助手册 man命令 用法:man 手册编号 命令名 2>用户切换 $ su Eric切换到Eric这个用户$ su切换到特权用户root 注意:Ubuntu默认情况没有合法root权限,不能直接使用su命令提升到root权限,只能使用sudo获取root权限 3>特权命令 $ sudo su切换到root用户$ sudo r…