目录
二 、在访问控制为RAM用户授予AliyunOSSFullAccess的权限
为什么客户端直传
在典型的服务端和客户端架构下,常见的文件上传方式是服务端代理上传:客户端将文件上传到业务服务器,然后业务服务器将文件上传到OSS。在这个过程中,一份数据需要在网络上传输两次,会造成网络资源的浪费,增加服务端的资源开销。为了解决这一问题,您可以在客户端直连OSS来完成文件上传,无需经过业务服务器中转。
服务端生成签名以实现Web端直传,使用户能够通过Web浏览器直接使用PostObject接口上传文件到OSS。此过程通过签名机制确保上传的安全性。同时,根据具体业务需求,您可以在服务端生成上传策略(Policy),以限制上传操作。
方案概览
服务端生成签名实现Web端直传的过程如下:
要实现服务端签名直传,只需3步:
说明
对于需要限制上传文件属性的场景,您可以在服务端生成PostObject所需的Post签名、PostPolicy等信息,然后客户端可以凭借这些信息,在一定的限制下不依赖OSS SDK直接上传文件。您可以借助服务端生成的PostPolicy限制客户端上传的文件,例如限制文件大小、文件类型。此方案适用于通过HTML表单上传的方式上传文件。需要注意的是,此方案不支持基于分片上传大文件、基于分片断点续传的场景。
配置OSS:配置OSS,在控制台创建一个Bucket,用于存储用户上传的文件。同时,为 Bucket 配置跨域资源共享(CORS) 规则,以允许来自服务端的跨域请求。
配置服务端:配置服务端,服务端生成PostObject所需的签名和Post Policy,然后使用访问凭证和服务端预设的上传策略(如Bucket名称、目录路径、过期时间等)生成签名授权用户在一定时间内进行文件上传。
配置Web端:配置Web端,构造HTML表单请求,通过表单提交使用签名将文件上传到OSS。
步骤一:配置OSS
一、创建Bucket
创建一个OSS Bucket,用于存储Web应用在浏览器环境中直接上传的文件。
登录OSS管理控制台。
在左侧导航栏,单击Bucket 列表,然后单击创建 Bucket。
在创建 Bucket面板,选择快捷创建,按如下说明配置各项参数。
参数
示例值
Bucket名称
web-direct-upload
地域
华东1(杭州)
点击完成创建。
二、配置CORS规则
为创建的OSS Bucket配置CORS规则。
访问Bucket列表,然后单击目标Bucket名称。
在跨域设置页面,单击创建规则。
在创建跨域规则面板,按以下说明设置跨域规则。
参数
示例值
来源
*
允许Methods
POST、PUT、GET
允许Headers
*
单击确定。
步骤二:配置服务端
一、配置用户权限
说明
为了确保部署完成后不会因为操作未授权而导致文件上传到OSS失败,建议您先按照以下步骤创建RAM用户并配置相应的权限。
在访问控制创建RAM用户
首先,创建一个调用方式为OpenAPI调用的RAM用户,并获取对应的访问密钥,作为业务服务器的应用程序的长期身份凭证。
使用云账号或账号管理员登录RAM控制台。
在左侧导航栏,选择身份管理 > 用户。
单击创建用户。
输入登录名称和显示名称。
在调用方式区域下,选择OpenAPI调用,然后单击确定。
重要
RAM用户的AccessKey Secret只在创建时显示,后续不支持查看,请妥善保管。
单击操作下的复制,保存调用密钥(AccessKey ID和AccessKey Secret)。
二 、在访问控制为RAM用户授予AliyunOSSFullAccess的权限
创建RAM用户后,需要授予RAM用户AliyunOSSFullAccess的权限,使其可以通过扮演RAM角色来获取管理对象存储服务(OSS)权限
在左侧导航栏,选择身份管理 > 用户。
在用户页面,找到目标RAM用户,然后单击RAM用户右侧的添加权限。
在新增授权页面,选择AliyunOSSFullAccess系统策略。
单击确认新增授权
步骤三 .服务端获取访问凭证 & 配置Web端
您可以通过OSS控制台的PostObject Policy签名工具为通过HTML表单上传生成请求签名。通过PostObject Policy签名工具填入指定参数后,会自动生成请求签名,并校验请求签名的正确性。
-
参数
是否必选
示例值
说明
AccessKeyId
是
LTAI********
填写阿里云账号或RAM用户的访问密钥AccessKey,包括AccessKey ID和AccessKey Secret。
AccessKeySecret
是
KZo1********
过期时间
是
2023-01-09T07:36:58.086Z
请求过期时间,必须为GMT格式。下拉选择过期时间后会自定填充到Policy。
Policy
否
{ "expiration": "2014-12-01T12:00:00.000Z", "conditions": [ {"bucket": "johnsmith" }, ["content-length-range", 1, 10], ["eq", "$success_action_status", "201"], ["starts-with", "$key", "user/eric/"], ["in", "$content-type", ["image/jpg", "image/png"]], ["not-in", "$cache-control", ["no-cache"]] ]}
PostObject请求的Policy表单域,用于验证请求的合法性。Policy为一段经过UTF-8和Base64编码的JSON文本,声明了PostObject请求必须满足的条件。
重要
对于向公共读写的Bucket执行表单上传时,Policy表单域为可选项,但强烈建议使用该域来限制PostObject请求。
关于Post Policy的更多信息,请参见附录:Post Policy。
服务端生成Post签名和Post Policy
服务端生成Post签名和Post Policy等信息的示例代码如下:
ALI_ACCESS_KEY_ID,
ALI_ACCESS_KEY_SECRET,
ALI_OSS_BUCKET,
ALI_OSS_REGION,
} = require("../config/config.default");
const OSS = require("ali-oss");
const moment = require("moment");
const path = require("path");
// 验证配置项是否存在
if (
!ALI_ACCESS_KEY_ID ||
!ALI_ACCESS_KEY_SECRET ||
!ALI_OSS_BUCKET ||
!ALI_OSS_REGION
) {
throw new Error("Missing required configuration for Ali OSS");
}
/**
* 初始化 OSS 客户端
*/
const store = new OSS({
region: ALI_OSS_REGION, // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
accessKeyId: ALI_ACCESS_KEY_ID,
accessKeySecret: ALI_ACCESS_KEY_SECRET,
bucket: ALI_OSS_BUCKET, // yourBucketName填写Bucket名称。
});
/**
* 计算 Post 签名
* @param {string} directory - 上传目录
* @param {string} name - 文件名
* @returns {Promise<Object>} - 签名结果
*/
async function calculatePostSignature(directory, name) {
const date = moment().add(1, "days");
const key = `${directory}/${name}`;
const policy = {
expiration: date.toISOString(), // 过期时间
// 条件
conditions: [
["content-length-range", 0, 5 * 1024 * 1024], // 文件大小限制
{ bucket: ALI_OSS_BUCKET }, // 存储空间
["eq", "$key", key], // 文件名 eq:等于
[
"in",
"$content-type",
["image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp"],
], // 文件类型限制 in: 包含
],
};
const formData = store.calculatePostSignature(policy);
// 获取主机
let host = await store.getBucketLocation();
// 构建 URL
let url = `http://${ALI_OSS_BUCKET}.${host.location}.aliyuncs.com`.toString();
return {
policy: formData.policy, // 策略
signature: formData.Signature, // 签名
ossAccessKeyId: formData.OSSAccessKeyId, // 访问凭证
host: url, // 主机
dir: directory, // 目录
key: key, // 文件名
};
}
注册使用
// 获取 oss 上传的 token
async getOssToken(ctx, next) {
let { filename } = ctx.request.query;
console.log(filename);
const directory = "uploads/images";
// 生成唯一文件名 (唯一id+文件扩展名)
const uniqueFileName = `${uuidv4()}.${filename.split(".")[1]}`;
const res = await calculatePostSignature(directory, uniqueFileName);
ctx.body = {
code: 0,
message: "获取oss上传token成功",
result: res,
};
}
客户端示例代码
Web端使用临时访问凭证上传文件到OSS的示例代码如下:
1.调用接口获取签名等信息
2 调用后端 返回的上传oss的地址
注意 要使用后的返回的文件名进行上传,这样可以保证唯一性,并且 政策上有检验如果不是也会上传失败
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<form>
<input type="file" name="file" id="file" />
<button type="submit">上传</button>
</form>
<body>
</body>
<script>
const form = document.querySelector("form");
const fileInput = document.querySelector("#file");
form.addEventListener("submit", (event) => {
event.preventDefault(); // 阻止表单提交的默认行为
const file = fileInput.files[0];
const filename = fileInput.files[0].name;
let type = fileInput.files[0].type;
fetch("http://localhost:9999/goods/oss/token?filename=" + filename, { method: "GET" })
.then((response) => {
if (!response.ok) {
throw new Error("获取签名失败");
}
return response.json();
})
.then((res) => {
let data = res.result;
const formData = new FormData();
formData.append("name", filename);
formData.append("policy", data.policy); // 策略
formData.append("OSSAccessKeyId", data.ossAccessKeyId);// 阿里云的key
formData.append("success_action_status", "200"); // 200 表示上传成功后,返回200状态码
formData.append("signature", data.signature); // 签名
formData.append("key", data.key); // 文件名
formData.append("file", file); // 文件
return fetch(data.host, { method: "POST", body: formData });
})
.then((response) => {
if (response.ok) {
console.log("上传成功");
alert("文件已上传");
} else {
console.log("上传失败", response);
alert("上传失败,请稍后再试");
}
})
.catch((error) => {
console.error("发生错误:", error);
});
});
</script>
</html>