对于需要限制上传文件属性的场景,您可以在服务端生成PostObject所需的Post签名、PostPolicy等信息,然后客户端可以凭借这些信息,在一定的限制下不依赖OSS SDK直接上传文件。您可以借助服务端生成的PostPolicy限制客户端上传的文件,例如限制文件大小、文件类型。此方案适用于通过HTML表单上传的方式上传文件。需要注意的是,此方案不支持基于分片上传大文件、基于分片断点续传的场景。
请求流程
以下以集成STS完成服务端签名直传为例进行说明。集成STS后,客户端能够在不直接接触长期凭证的情况下,利用STS Token完成表单上传,增强上传过程的安全性。
客户端向业务服务器请求上传策略。
客户端首先向业务服务器发送请求,请求中应包含所需上传操作的最小权限需求(例如上传到特定Bucket的权限)和期望的有效时间等信息。不同于直接请求Post签名和Post Policy,客户端需要请求的是一个带有特定权限限制的STS Token以及上传策略。
业务服务器向STS服务请求获取STS Token。
业务服务器收到客户端请求后,使用长期凭证向阿里云STS服务发起请求,生成一个具有限定权限和有效时间的STS Token。
STS服务向业务服务器返回STS Token。
业务服务器根据STS Token和其他上传限制生成上传策略。
业务服务器根据返回的STS Token和其他上传限制条件(例如Bucket名称、目录路径、过期时间等)生成一个安全的上传策略(Policy),并返回给客户端。
客户端构造并提交表单上传请求。
客户端使用收到的STS Token和上传策略,结合文件信息构造HTML表单。此时,上传策略中的签名和STS Token的加入使得客户端可以直接与OSS交互,而无需暴露业务服务器的长期凭证。
OSS返回成功响应给客户端。
前提条件
权限说明
要使用服务端签名直传时,您必须拥有oss:PutObject
权限。具体操作,请参见为RAM用户授权。
方案部署
本方案部署的示例工程:server-signed-direct-upload.zip
自动部署
您可以点击一键部署通过ROS快速部署服务端签名直传。部署完成后,复制输出页签下OssClientAddress的值,通过浏览器上传文件。
手动部署
准备工作
创建RAM用户。
登录RAM控制台。
在左侧导航栏,选择身份管理>用户。
单击创建用户。
输入登录名称和显示名称。
在访问方式区域下,选择OpenAPI调用访问,然后单击确定。
根据界面提示,完成安全验证。
复制访问密钥(AccessKey ID和AccessKey Secret)。
重要RAM用户的AccessKey Secret只在创建时显示,后续不支持查看,请妥善保管。
为RAM用户授予请求AssumeRole的权限。
创建RAM用户后,您需要授予RAM用户通过扮演角色来调用STS服务的权限。
单击已创建RAM用户右侧对应的添加权限。
在添加权限页面,选择AliyunSTSAssumeRoleAccess系统策略。
说明授予RAM用户调用STS服务AssumeRole接口的固定权限是AliyunSTSAssumeRoleAccess,与后续获取临时访问凭证以及通过临时访问凭证发起OSS请求所需权限无关。
单击确定。
创建RAM角色。
您需要创建RAM角色,用于定义RAM角色被扮演时,可以获得OSS服务的哪些访问权限。
在左侧导航栏,选择身份管理>角色。
单击创建角色,选择可信实体类型为阿里云账号,单击下一步。
在创建角色对话框,角色名称填写为RamOssTest,选择信任的云账号为当前云账号。
单击完成。角色创建完成后,单击关闭。
在角色页面,搜索框输入角色名称RamOssTest,然后单击RamOssTest。
单击ARN右侧的复制,保存角色的ARN。
为RAM角色授予上传文件的权限。
为RAM角色附加权限策略,明确RAM角色在被扮演时所能拥有的OSS资源访问权限。结合本实例教程,希望RAM用户在扮演该角色后只能向OSS指定Bucket上传文件,则需要为角色添加写入权限的策略。
创建上传文件的自定义权限策略。
在左侧导航栏,选择权限管理>权限策略。
在权限策略页面,单击创建权限策略。
在创建权限策略页面,单击脚本编辑,然后在策略文档输入框中赋予角色上传文件到examplebucket的权限。具体配置示例如下。
{ "Version": "1", "Statement": [ { "Effect": "Allow", "Action": [ "oss:PutObject" ], "Resource": [ "acs:oss:*:*:examplebucket/*" ] } ] }
策略配置完成后,单击继续编辑基本信息。
在基本信息区域,填写策略名称为RamTestPolicy,然后单击确定。
为RAM角色RamOssTest授予自定义权限策略。
在左侧导航栏,选择
。在角色页面,找到目标RAM角色RamOssTest。
单击RAM角色RamOssTest右侧的新增授权。
在添加权限页面下的自定义策略页签,选择已创建的自定义权限策略RamTestPolicy。
单击确定。
操作步骤
修改示例工程源码server文件夹下的config.js配置文件。
module.exports = { // 从环境变量中获取RAM用户的访问密钥和目标RAM角色的Arn. accessKeyId: process.env.OSS_ACCESS_KEY_ID, accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET, roleArn: process.env.OSS_STS_ROLE_ARN, // region填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。 region: "oss-cn-hangzhou", // 指定Bucket名称。 bucket: "examplebucket", };
业务服务器请求获取STS Token、并根据STS Token和其他上传限制生成上传策略。
const { exec } = require("child_process"); const path = require("path"); const express = require("express"); const cors = require("cors"); const moment = require("moment"); const OSS = require("ali-oss"); const { STS } = require("ali-oss"); const config = require("./config"); const getToken = async () => { const { accessKeyId, accessKeySecret, roleArn, bucket } = config; const seconds = 3000; //过期时间为3000秒。 const date = new Date(); date.setSeconds(date.getSeconds() + seconds); const dir = "user-dirs/"; const policy = { expiration: date.toISOString(), // 请求有效期。 conditions: [ ["content-length-range", 0, 1048576000], // 设置上传文件的大小限制。 ["starts-with", "$key", dir], // 限制文件只能上传到user-dirs目录下。 { bucket }, // 限制文件只能上传至指定Bucket。 ], }; /* 使用stsToken的方式上传。*/ let stsToken; if (roleArn) { let sts = new STS({ accessKeyId, accessKeySecret, }); const { credentials: { AccessKeyId, AccessKeySecret, SecurityToken }, } = await sts.assumeRole(roleArn, "", seconds, "sessiontest"); stsToken = SecurityToken; client = new OSS({ accessKeyId: AccessKeyId, accessKeySecret: AccessKeySecret, stsToken, }); } // 计算签名。 const formData = await client.calculatePostSignature(policy); // 返回参数。 const params = { expire: moment(date).unix().toString(), policy: formData.policy, signature: formData.Signature, accessid: formData.OSSAccessKeyId, stsToken, host: `http://${config.bucket}.${config.region}.aliyuncs.com`, dir, }; return params; }; const app = express(); app.use(cors()); // 按需生成STS Token。每次访问/token接口时,都会调用getToken函数生成一个新的STS Token。 // 结合以上示例在有效期3000秒内生成的STS Token可以重复使用。超过有效期后,再次请求/token接口时,将会生成一个新的STS Token。 app.get("/token", async (req, res) => { const result = await getToken(); res.header["Access-Control-Allow-Origin"] = "*"; res.json(result); }); app.get(/^(.+)*\.(html|js|ico)$/i, async (req, res) => { const pat = path.join(__dirname, "../", req.originalUrl); res.sendFile(pat); }); const url = "http://127.0.0.1:3001/index.html"; app.listen(3001, () => console.log("请打开:" + url)); if (process.platform === "win32") { exec(`start ${url}`); // Windows系统 } else if (process.platform === "darwin") { exec(`open ${url}`); // macOS系统 } else { exec(`xdg-open ${url}`); // Linux系统 }
业务服务器向客户端返回STS Token和上传策略。
{ "expire": "1716879673", "policy": "eyJl****", "signature": "YGTr****", "accessid": "STS.NULw****", "stsToken": "CAIS****", "host": "http://examplebucket.oss-cn-hangzhou.aliyuncs.com", "dir": "user-dirs/" }
Body中的各字段说明如下:
字段
描述
expire
由服务器端指定的Policy过期时间,格式为Unix时间戳(自UTC时间1970年01月01号开始的秒数)。
policy
用户表单上传的策略(Policy),Policy为经过Base64编码过的字符串。详情请参见Post Policy。
signature
对Policy签名后的字符串。详情请参见Post Signature。
accessid
临时访问密钥AccessKey ID。
stsToken
安全令牌。
host
Bucket域名。
dir
限制上传的文件前缀。
客户端构造并提交表单上传请求。
说明除file表单域外,包含key在内的其他所有表单域的大小均不能超过8 KB。
客户端上传默认同名覆盖,如果您不希望覆盖同名文件,可以在上传请求的header中携带参数x-oss-forbid-overwrite,并指定其值为true。当您上传的文件在OSS中存在同名文件时,该文件会上传失败,并返回FileAlreadyExists错误。
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <title>OSS web直传</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> </head> <body> <h2>OSS web直传---在服务端用Node.js签名</h2> <ol> <li>Bucket必须设置跨域,且允许Methods必须选中POST,否则无法执行表单上传。</li> </ol> <br /> <div> <input type="file" id="fileInput" name="fileInput" /> <input type="button" value="开始上传" onclick="upload()" /> </div> <script> function upload() { const tokenUrl = "http://127.0.0.1:3001/token"; fetch(tokenUrl).then(async (res) => { const { policy, signature, accessid, host, dir, stsToken } = await res.json(); let formData = new FormData(); formData.append("success_action_status", "200"); // 指定成功上传时,服务端返回状态码200,默认返回204。 formData.append("policy", policy); formData.append("signature", signature); formData.append("OSSAccessKeyId", accessid); if (stsToken) formData.append("x-oss-security-token", stsToken); const files = document.getElementById("fileInput").files; if (files.length === 0) { alert("请选择文件"); return; } formData.append("key", dir + files[0].name); // 文件名 formData.append("file", files[0]); // file必须为最后一个表单域 const param = { method: "POST", body: formData, }; fetch(host, param) .then((data) => { console.log(data); alert("上传成功"); }) .catch((error) => { console.error("Error:", error); }); }); } </script> </body> </html>
相关文档
大多数情况下,应用服务器需要了解用户上传了哪些文件以及对应的文件名称等信息。如果上传的是图片,还希望获取图片大小等。此时,您可以通过上传回调方案实现该需求。关于服务端签名直传并设置上传回调的介绍,请参见服务器端签名直传并设置上传回调。