添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
腼腆的甘蔗  ·  FTTH Dual Mode ONU ...·  2 月前    · 
酒量大的莲藕  ·  Maven Central: ...·  7 月前    · 
耍酷的木瓜  ·  Google Data Studio ...·  9 月前    · 
服务端签名直传

服务端签名直传

更新时间:
一键部署
我的部署

您可以使用 PostObject 接口,将文件直接从 Web 端上传到 OSS,服务器生成的签名为直传操作提供安全保障,同时支持配置上传策略(Policy)以限制上传操作并满足业务需求。

方案概览

服务端生成签名实现 Web 端直传的过程如下:

image

要实现服务端签名直传,只需 3 步:

说明

由于使用了临时访问凭证,整个过程中不会泄露业务服务器的长期密钥,保证了文件上传的安全性。

  1. 配置 OSS :配置 OSS,在控制台创建一个 Bucket,用于存储用户上传的文件。同时,为 Bucket 配置跨域资源共享(CORS) 规则,以允许来自服务端的跨域请求。

  2. 配置服务端: 配置服务端,调用 STS 服务获取一个临时访问凭证,然后使用临时访问凭证和服务端预设的上传策略(如 Bucket 名称、目录路径、过期时间等)生成签名授权用户在一定时间内进行文件上传。

  3. 配置 Web :配置 Web 端,构造 HTML 表单请求,通过表单提交使用签名将文件上传到 OSS。

示例工程

操作步骤

步骤一:配置 OSS

一、创建 Bucket

创建一个 OSS Bucket,用于存储 Web 应用在浏览器环境中直接上传的文件。

  1. 登录 OSS 管理控制台

  2. 在左侧导航栏,单击 Bucket 列表 然后单击 创建 Bucket

  3. 创建 Bucket 面板,选择快捷创建,按如下说明配置各项参数。

    参数

    示例值

    Bucket 名称

    web-direct-upload

    地域

    华东 1(杭州)

  4. 点击 完成创建

二、配置 CORS 规则

为创建的 OSS Bucket 配置 CORS 规则。

  1. 访问 Bucket 列表 ,然后单击目标 Bucket 名称。

  2. 跨域设置 页面,单击 创建规则

  3. 创建跨域规则 面板,按以下说明设置跨域规则。

    参数

    示例值

    来源

    *

    允许 Methods

    POST、PUT、GET

    允许 Headers

    *

  1. 单击 确定

步骤二:配置服务端

说明

在实际部署时,如果您已经有自己的业务服务器,则无需进行准备工作,直接跳转到 一、配置用户权限

准备工作:创建一台 ECS 实例 作为业务服务器

操作一:创建 ECS 实例

请您进入 自定义购买 页面,并根据如下各模块的内容,创建或选择购买 ECS 实例所需的基础资源。

  1. 选择地域 & 付费类型

    1. 根据业务需求,选择合适的付费类型。本文选择 按量付费 模式,此模式操作相对灵活。

    2. 基于业务场景对时延的要求,选择地域。通常来说离 ECS 实例的物理距离越近,网络时延越低,访问速度越快。本文以选择 华东 1(杭州) 为例。

      image

  1. 创建专有网络 VPC & 交换机

    创建 VPC 时,请您选择和 ECS 相同的地域,并根据业务需求规划网段。本文以创建 华东 1(杭州) 地域的 VPC 和交换机为例。创建完毕后返回 ECS 购买页,刷新并选择 VPC 及交换机。

    说明

    创建 VPC 时,可同时创建交换机。

    image

    image

    image

  1. 选择规格 & 镜像

    选择实例的规格及镜像,镜像为实例确定安装的操作系统及版本。本文选择的实例规格为 ecs.e-c1m1.large ,在满足测试需求的同时,价格较为实惠。镜像为公共镜像 Alibaba Cloud Linux 3.2104 LTS 64

    image

  1. 选择存储

    ECS 实例选择系统盘,并按需选择数据盘。本文实现简单 Web 系统搭建,只需要系统盘存储操作系统,无需数据盘。

    image

  1. 绑定公网 IP

    本实例需要支持公网访问。为了简化操作,本文选择直接为实例分配公网 IP。您也可以在创建实例后,为实例绑定弹性公网 IP,具体操作,请参见 EIP 绑定至 ECS 实例

    说明
    • 若未绑定公网 IP,将无法使用 SSH RDP 通过公网直接访问实例,也无法通过公网验证实例中 Web 服务的搭建。

    • 本文选择 按使用流量 的带宽计费模式。此模式只需为所消耗的公网流量付费。更多信息,请参见 公网带宽计费

    image

  1. 创建安全组

    为实例创建安全组。安全组是一种虚拟网络防火墙,能够控制 ECS 实例的出入流量。创建时,需要设置放行以下指定端口,便于后续访问 ECS 实例。

    端口范围 :SSH(22)、RDP(3389)、HTTP(80)、HTTPS(443)。

    说明
    • 端口范围 处选中的是 ECS 实例上运行的应用需开放的端口。

    • 此处创建的安全组默认设置 0.0.0.0/0 作为源的规则。0.0.0.0/0 表示允许全网段设备访问指定的端口,如果您知道请求端的 IP 地址,建议后续设置为具体的 IP 范围。具体操作,请参见 修改安全组规则

    image

  1. 创建密钥对

    1. 密钥对可作为登录时证明个人身份的安全凭证,创建完成后,必须下载私钥,以供后续 连接 ECS 实例 时使用。创建完毕后返回 ECS 购买页,刷新并选择密钥对。

    2. root 具有操作系统的最高权限,使用 root 作为登录名可能会导致安全风险,建议您选择 ecs-user 作为登录名。

      说明

      创建密钥对后,私钥会自动下载,请您关注浏览器的下载记录,保存 .pem 格式的私钥文件。

      image

  1. 创建并查看 ECS 实例

    创建或选择好 ECS 实例所需的基础资源后,单击 确认下单 。在提示成功的对话框中,单击 管理控制台 ,即可在控制台查看到创建好的 ECS 实例。请您保存以下数据,以便在后续操作中使用。

    • 实例 ID :便于在实例列表中查询到该实例。

    • 地域 :便于在实例列表中查询到该实例。

    • 公网 IP 地址 :便于在后续使用 ECS 实例时,做 Web 服务的部署结果验证。

    image image

操作二: 连接 ECS 实例

  1. 云服务器 ECS 控制台 实例列表 页面,根据地域、实例 ID 找到创建好的 ECS 实例,单击 操作 列下的 远程连接 image

  2. 远程连接 对话框中,单击 通过 Workbench 远程连接 对应的 立即登录 image

  3. 登录实例 对话框中,选择 认证方式 SSH 密钥认证 ,用户名为 ecs-user ,输入或上传创建密钥对时下载的私钥文件,单击 确定 ,即可登录 ECS 实例。

    说明

    私钥文件在 创建密钥对 时自动下载到本地,请您关注浏览器的下载记录,查找 .pem 格式的私钥文件。

    image

  4. 显示如下页面后,即说明您已成功登录 ECS 实例。 image

一、配置用户权限

说明

为了确保部署完成后不会因为操作未授权而导致文件上传到 OSS 失败,建议您先按照以下步骤创建 RAM 用户并配置相应的权限。

操作一: 在访问控制创建 RAM 用户

首先,创建一个 RAM 用户,并获取对应的访问密钥,作为业务服务器的应用程序的长期身份凭证。

  1. 使用云账号或账号管理员登录 RAM 控制台

  2. 在左侧导航栏,选择 身份管理 > 用户

  3. 单击 创建用户

  4. 输入 登录名称 显示名称

  5. 访问方式 区域下,选择 使用永久 AccessKey 访问 ,然后单击 确定

重要

RAM 用户的 AccessKey Secret 只在创建时显示,后续不支持查看,请妥善保管。

  1. 单击 操作 下的 复制 ,保存调用密钥(AccessKey ID AccessKey Secret)。

操作二: 在访问控制为 RAM 用户授予调用 AssumeRole 接口的权限

创建 RAM 用户后,需要授予 RAM 用户调用 STS 服务的 AssumeRole 接口的权限,使其可以通过扮演 RAM 角色来获取临时身份凭证。

  1. 在左侧导航栏,选择 身份管理 > 用户

  2. 用户 页面,找到目标 RAM 用户,然后单击 RAM 用户右侧的 添加权限

  3. 新增授权 页面,选择 AliyunSTSAssumeRoleAccess 系统策略。

    说明

    授予 RAM 用户调用 STS 服务 AssumeRole 接口的固定权限是 AliyunSTSAssumeRoleAccess ,与后续获取临时访问凭证以及通过临时访问凭证发起 OSS 请求所需权限无关。

    image

  4. 单击 确认新增授权

操作三: 在访问控制创建 RAM 角色

为当前云账号创建一个 RAM 角色,并获取对应的 角色的 ARN (Aliyun Resource Name,阿里云资源名称),用于 RAM 用户之后进行扮演。

  1. 在左侧导航栏,选择 身份管理 > 角色

  2. 单击 创建角色 ,可信实体类型选择 云账号

  3. 选择当前云账号,单击 确定

  4. 填写角色名称,单击 确定

  5. RAM 角色管理页面,单击 复制 ,保存角色的 ARN。

1.png

操作四: 在访问控制创建 上传文件 的权限策略

按照最小授权原则,为 RAM 角色创建一个自定义权限策略,限制只能向指定 OSS 的存储空间进行上传操作。

  1. 在左侧导航栏,选择 权限管理 > 权限策略

  2. 单击 创建权限策略

  3. 创建权限策略 页面,单击 脚本编辑 ,将以下脚本中的 <Bucket 名称> 替换为准备工作中创建的 Bucket 名称 web-direct-upload

    {
      "Version": "1",
      "Statement": [
          "Effect": "Allow",
          "Action": "oss:PutObject",
          "Resource": "acs:oss:*:*:<Bucket名称>/*"
    }
  4. 策略配置完成后,单击 继续编辑基本信息

  5. 基本信息 区域,填写策略名称,然后单击 确定

操作五: 在访问控制为 RAM 角色授予权限

RAM 角色授予创建的自定义权限,以便该 RAM 角色被扮演时能获取所需的权限。

  1. 在左侧导航栏,选择 身份管理 > 角色

  2. 角色 页面,找到目标 RAM 角色,然后单击 RAM 角色右侧的 新增授权

  3. 新增授权 页面下,选择 自定义策略 ,选择已创建的自定义权限策略。

    image

  4. 单击 确定

二、服务端获取临时访问凭证并计算签名

说明

建议您先将敏感信息(如 accessKeyId accessKeySecret roleArn )配置到环境变量,从而避免在代码里显式地配置,降低泄露风险。

您可以仅在当前会话中使用该环境变量,可以参照以下步骤添加临时环境变量。

Linux 系统

  1. 执行以下命令。

    export OSS_ACCESS_KEY_ID="your-access-key-id"
    export OSS_ACCESS_KEY_SECRET="your-access-key-secret"
    export OSS_STS_ROLE_ARN="your-role-arn"
  2. 执行以下命令,验证该环境变量是否生效。

    echo $OSS_ACCESS_KEY_ID
    echo $OSS_ACCESS_KEY_SECRET
    echo $OSS_STS_ROLE_ARN

macOS 系统

  1. 执行以下命令。

    export OSS_ACCESS_KEY_ID="your-access-key-id"
    export OSS_ACCESS_KEY_SECRET="your-access-key-secret"
    export OSS_STS_ROLE_ARN="your-role-arn"
  2. 执行以下命令,验证该环境变量是否生效。

    echo $OSS_ACCESS_KEY_ID
    echo $OSS_ACCESS_KEY_SECRET
    echo $OSS_STS_ROLE_ARN

Windows 系统

  1. CMD 中运行以下命令。

    set OSS_ACCESS_KEY_ID "your-access-key-id"
    set OSS_ACCESS_KEY_SECRET "your-access-key-secret"
    set OSS_STS_ROLE_ARN "your-role-arn"
  2. 打开一个新的 CMD 窗口。

  3. 在新的 CMD 窗口运行以下命令,检查环境变量是否生效。

    echo $OSS_ACCESS_KEY_ID
    echo $OSS_ACCESS_KEY_SECRET
    echo $OSS_STS_ROLE_ARN

您可以参考以下代码,在服务端进行 POST 签名版本 4(推荐) 的计算工作。关于 policy 表单域详细配置信息,请参见 policy 表单域

Java

Maven 项目中,导入以下依赖。

<!-- https://mvnrepository.com/artifact/com.aliyun/credentials-java -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>credentials-java</artifactId>
    <version>0.3.4</version>
</dependency>
<dependency>
    <groupId>com.aliyun.kms</groupId>
    <artifactId>kms-transfer-client</artifactId>
    <version>0.1.0</version>
</dependency>
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>
<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>sts20150401</artifactId>
  <version>1.1.6</version>
</dependency>

您可以参考如下代码来完成服务端获取临时访问凭证并计算 POST 签名:

package com.aliyun.oss.web;
import com.aliyun.sts20150401.models.AssumeRoleResponse;
import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
import com.aliyun.tea.TeaException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Controller
public class WebController {
    //OSS基础信息 替换为实际的 bucket 名称、 region-id、host。
    String bucket = "examplebucket";
    String region = "cn-hangzhou";
    String host = "http://examplebucket.oss-cn-hangzhou.aliyuncs.com";
    // 设置上传回调URL(即回调服务器地址),必须为公网地址。用于处理应用服务器与OSS之间的通信,OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
    //限定上传到OSS的文件前缀。
    String upload_dir = "dir";
    //指定过期时间,单位为秒。
    Long expire_time = 3600L;
     * 通过指定有效的时长(秒)生成过期时间。
     * @param seconds 有效时长(秒)。
     * @return ISO8601 时间字符串,如:"2014-12-01T12:00:00.000Z"。
    public static String generateExpiration(long seconds) {
        // 获取当前时间戳(以秒为单位)
        long now = Instant.now().getEpochSecond();
        // 计算过期时间的时间戳
        long expirationTime = now + seconds;
        // 将时间戳转换为Instant对象,并格式化为ISO8601格式
        Instant instant = Instant.ofEpochSecond(expirationTime);
        // 定义时区
        ZoneId zone = ZoneId.systemDefault();  // 使用系统默认时区
        // 将 Instant 转换为 ZonedDateTime
        ZonedDateTime zonedDateTime = instant.atZone(zone);
        // 定义日期时间格式,例如2023-12-03T13:00:00.000Z
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        // 格式化日期时间
        String formattedDate = zonedDateTime.format(formatter);
        // 输出结果
        return formattedDate;
    //初始化STS Client
    public static com.aliyun.sts20150401.Client createStsClient() throws Exception {
        // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
        // 建议使用更安全的 STS 方式。
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                // 必填,请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_ID。
                .setAccessKeyId(System.getenv("OSS_ACCESS_KEY_ID"))
                // 必填,请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_SECRET。
                .setAccessKeySecret(System.getenv("OSS_ACCESS_KEY_SECRET"));
        // Endpoint 请参考 https://api.aliyun.com/product/Sts
        config.endpoint = "sts.cn-hangzhou.aliyuncs.com";
        return new com.aliyun.sts20150401.Client(config);
    //获取STS临时凭证
    public static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials getCredential() throws Exception {
        com.aliyun.sts20150401.Client client = WebController.createStsClient();
        com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest()
                // 必填,请确保代码运行环境设置了环境变量 OSS_STS_ROLE_ARN
                .setRoleArn(System.getenv("OSS_STS_ROLE_ARN"))
                .setRoleSessionName("yourRoleSessionName");// 自定义会话名称
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        try {
            // 复制代码运行请自行打印 API 的返回值
            AssumeRoleResponse response = client.assumeRoleWithOptions(assumeRoleRequest, runtime);
            // credentials里包含了后续要用到的AccessKeyId、AccessKeySecret和SecurityToken。
            return response.body.credentials;
        } catch (TeaException error) {
            // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
            // 错误 message
            System.out.println(error.getMessage());
            // 诊断地址
            System.out.println(error.getData().get("Recommend"));
            com.aliyun.teautil.Common.assertAsString(error.message);
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
            // 错误 message
            System.out.println(error.getMessage());
            // 诊断地址
            System.out.println(error.getData().get("Recommend"));
            com.aliyun.teautil.Common.assertAsString(error.message);
        return null;
    @GetMapping("/get_post_signature_for_oss_upload")
    public ResponseEntity<Map<String, String>> getPostSignatureForOssUpload() throws Exception {
        AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials sts_data = getCredential();
        String accesskeyid =  sts_data.accessKeyId;
        String accesskeysecret =  sts_data.accessKeySecret;
        String securitytoken =  sts_data.securityToken;
        //获取x-oss-credential里的date,当前日期,格式为yyyyMMdd
        LocalDate today = LocalDate.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = today.format(formatter);
        //获取x-oss-date
        ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
        String x_oss_date = now.format(formatter2);
        // 步骤1:创建policy。
        String x_oss_credential = accesskeyid + "/" + date + "/" + region + "/oss/aliyun_v4_request";
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> policy = new HashMap<>();
        policy.put("expiration", generateExpiration(expire_time));
        List<Object> conditions = new ArrayList<>();
        Map<String, String> bucketCondition = new HashMap<>();
        bucketCondition.put("bucket", bucket);
        conditions.add(bucketCondition);
        Map<String, String> securityTokenCondition = new HashMap<>();
        securityTokenCondition.put("x-oss-security-token", securitytoken);
        conditions.add(securityTokenCondition);
        Map<String, String> signatureVersionCondition = new HashMap<>();
        signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
        conditions.add(signatureVersionCondition);
        Map<String, String> credentialCondition = new HashMap<>();
        credentialCondition.put("x-oss-credential", x_oss_credential); // 替换为实际的 access key id
        conditions.add(credentialCondition);
        Map<String, String> dateCondition = new HashMap<>();
        dateCondition.put("x-oss-date", x_oss_date);
        conditions.add(dateCondition);
        conditions.add(Arrays.asList("content-length-range", 1, 10240000));
        conditions.add(Arrays.asList("eq", "$success_action_status", "200"));
        conditions.add(Arrays.asList("starts-with", "$key", upload_dir));
        policy.put("conditions", conditions);
        String jsonPolicy = mapper.writeValueAsString(policy);
        // 步骤2:构造待签名字符串(StringToSign)。
        String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes()));
        // System.out.println("stringToSign: " + stringToSign);
        // 步骤3:计算SigningKey。
        byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), date);
        byte[] dateRegionKey = hmacsha256(dateKey, region);
        byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
        byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");
        // System.out.println("signingKey: " + BinaryUtil.toBase64String(signingKey));
        // 步骤4:计算Signature。
        byte[] result = hmacsha256(signingKey, stringToSign);
        String signature = BinaryUtil.toHex(result);
        // System.out.println("signature:" + signature);
        Map<String, String> response = new HashMap<>();
        // 将数据添加到 map 中
        response.put("version", "OSS4-HMAC-SHA256");
        // 这里是易错点,不能直接传policy,需要做一下Base64编码
        response.put("policy", stringToSign);
        response.put("x_oss_credential", x_oss_credential);
        response.put("x_oss_date", x_oss_date);
        response.put("signature", signature);
        response.put("security_token", securitytoken);
        response.put("dir", upload_dir);
        response.put("host", host);
        // 返回带有状态码 200 (OK) 的 ResponseEntity,返回给Web端,进行PostObject操作
        return ResponseEntity.ok(response);
    public static byte[] hmacsha256(byte[] key, String data) {
        try {
            // 初始化HMAC密钥规格,指定算法为HMAC-SHA256并使用提供的密钥。
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
            // 获取Mac实例,并通过getInstance方法指定使用HMAC-SHA256算法。
            Mac mac = Mac.getInstance("HmacSHA256");
            // 使用密钥初始化Mac对象。
            mac.init(secretKeySpec);
            // 执行HMAC计算,通过doFinal方法接收需要计算的数据并返回计算结果的数组。
            byte[] hmacBytes = mac.doFinal(data.getBytes());
            return hmacBytes;
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
}

Python

执行以下命令安装依赖。

pip install flask
pip install alibabacloud_tea_openapi alibabacloud_sts20150401 alibabacloud_credentials

请参考如下代码来完成服务端获取临时访问凭证 STStoken 并构建上传策略以计算 POST 签名。

from flask import Flask, render_template, jsonify
from alibabacloud_tea_openapi.models import Config
from alibabacloud_sts20150401.client import Client as Sts20150401Client
from alibabacloud_sts20150401 import models as sts_20150401_models
from alibabacloud_credentials.client import Client as CredentialClient
import os
import json
import base64
import hmac
import datetime
import time
import hashlib
app = Flask(__name__)
# 配置环境变量 OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_ID, OSS_STS_ROLE_ARN。
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET')
role_arn_for_oss_upload = os.environ.get('OSS_STS_ROLE_ARN')
# 自定义会话名称
role_session_name = 'yourRoleSessionName'  
# 替换为实际的bucket名称、region_id、host
bucket = 'examplebucket'
region_id = 'cn-hangzhou'
host = 'http://examplebucket.oss-cn-hangzhou.aliyuncs.com'
# 指定过期时间,单位为秒
expire_time = 3600  
# 指定上传到OSS的文件前缀。
upload_dir = 'dir'
def hmacsha256(key, data):
    计算HMAC-SHA256哈希值的函数
    :param key: 用于计算哈希的密钥,字节类型
    :param data: 要进行哈希计算的数据,字符串类型
    :return: 计算得到的HMAC-SHA256哈希值,字节类型
        mac = hmac.new(key, data.encode(), hashlib.sha256)
        hmacBytes = mac.digest()
        return hmacBytes
    except Exception as e:
        raise RuntimeError(f"Failed to calculate HMAC-SHA256 due to {e}")
@app.route("/")
def hello_world():
    return render_template('index.html')
@app.route('/get_post_signature_for_oss_upload', methods=['GET'])
def generate_upload_params():
    # 初始化配置,直接传递凭据
    config = Config(
        region_id=region_id,
        access_key_id=access_key_id,
        access_key_secret=access_key_secret
    # 创建 STS 客户端并获取临时凭证
    sts_client = Sts20150401Client(config=config)
    assume_role_request = sts_20150401_models.AssumeRoleRequest(
        role_arn=role_arn_for_oss_upload,
        role_session_name=role_session_name
    response = sts_client.assume_role(assume_role_request)
    token_data = response.body.credentials.to_map()
    # 使用 STS 返回的临时凭据
    temp_access_key_id = token_data['AccessKeyId']
    temp_access_key_secret = token_data['AccessKeySecret']
    security_token = token_data['SecurityToken']
    now = int(time.time())
    # 将时间戳转换为datetime对象
    dt_obj = datetime.datetime.utcfromtimestamp(now)
    # 在当前时间增加3小时,设置为请求的过期时间
    dt_obj_plus_3h = dt_obj + datetime.timedelta(hours=3)
    # 请求时间
    dt_obj_1 = dt_obj.strftime('%Y%m%dT%H%M%S') + 'Z'
    # 请求日期
    dt_obj_2 = dt_obj.strftime('%Y%m%d')
    # 请求过期时间
    expiration_time = dt_obj_plus_3h.strftime('%Y-%m-%dT%H:%M:%S.000Z')
    # 构建 Policy 并生成签名
    policy = {
        "expiration": expiration_time,
        "conditions": [
            ["eq", "$success_action_status", "200"],
            {"x-oss-signature-version": "OSS4-HMAC-SHA256"},
            {"x-oss-credential": f"{temp_access_key_id}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request"},
            {"x-oss-security-token": security_token},
            {"x-oss-date": dt_obj_1},  
    print(dt_obj_1)
    policy_str = json.dumps(policy).strip()
    # 步骤2:构造待签名字符串(StringToSign)
    stringToSign = base64.b64encode(policy_str.encode()).decode()
    # 步骤3:计算SigningKey
    dateKey = hmacsha256(("aliyun_v4" + temp_access_key_secret).encode(), dt_obj_2)
    dateRegionKey = hmacsha256(dateKey, "cn-hangzhou")
    dateRegionServiceKey = hmacsha256(dateRegionKey, "oss")
    signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request")
    # 步骤4:计算Signature
    result = hmacsha256(signingKey, stringToSign)
    signature = result.hex()
    # 组织返回数据
    response_data = {
        'policy': stringToSign,  #表单域
        'x_oss_signature_version': "OSS4-HMAC-SHA256",  #指定签名的版本和算法,固定值为OSS4-HMAC-SHA256
        'x_oss_credential': f"{temp_access_key_id}/{dt_obj_2}/cn-hangzhou/oss/aliyun_v4_request",  #指明派生密钥的参数集
        'x_oss_date': dt_obj_1,  #请求的时间
        'signature': signature,  #签名认证描述信息
        'host': host,
        'dir': upload_dir,
        'security_token': security_token  #安全令牌
    return jsonify(response_data)
if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8000)  # 如果需要监听其他地址如0.0.0.0,需要您自行在服务端添加认证机制

Node.js

执行以下命令安装依赖。

npm install ali-oss
npm install @alicloud/credentials
npm install express

请参考如下代码来完成服务端获取临时访问凭证 STStoken 并构建上传策略以计算 POST 签名。

const express = require('express');
const OSS = require('ali-oss');
const { STS } = require('ali-oss');
const { getCredential } = require('ali-oss/lib/common/signUtils');
const { getStandardRegion } = require('ali-oss/lib/common/utils/getStandardRegion');
const { policy2Str } = require('ali-oss/lib/common/utils/policy2Str');
const app = express();
const PORT = process.env.PORT || 8000; // 服务请求端口号
// 设置静态文件目录
app.use(express.static('templates'));
const GenerateSignature = async () => {
    // 初始化STS客户端
    let sts = new STS({
        accessKeyId: process.env.OSS_ACCESS_KEY_ID,  // 从环境变量中获取RAM用户的AccessKey ID
        accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET // 从环境变量中获取RAM用户的AccessKey Secret
    // 调用assumeRole接口获取STS临时访问凭证
    const result = await sts.assumeRole(process.env.OSS_STS_ROLE_ARN, '', '3600', 'yourRoleSessionName'); // 从环境变量中获取RAM角色ARN,并设置临时访问凭证有效期为3600秒,角色会话名称为yourRoleSessionName可自定义
    // 提取临时访问凭证中的AccessKeyId、AccessKeySecret和SecurityToken
    const accessKeyId = result.credentials.AccessKeyId;
    const accessKeySecret = result.credentials.AccessKeySecret;
    const securityToken = result.credentials.SecurityToken;
    // 初始化OSS Client
    const client = new OSS({
        bucket: 'examplebucket', // 请替换为目标Bucket名称
        region: 'cn-hangzhou', // 请替换为标Bucket所在地域
        accessKeyId,
        accessKeySecret,
        stsToken: securityToken,
        refreshSTSTokenInterval: 0,
        refreshSTSToken: async () => {
            const { accessKeyId, accessKeySecret, securityToken } = await client.getCredential();
            return { accessKeyId, accessKeySecret, stsToken: securityToken };
    // 创建表单数据Map
    const formData = new Map();
    // 设置签名过期时间为当前时间往后推10分钟 
    const date = new Date();
    const expirationDate = new Date(date);
    expirationDate.setMinutes(date.getMinutes() + 10);
    // 格式化日期为符合ISO 8601标准的UTC时间字符串格式
    function padTo2Digits(num) {
        return num.toString().padStart(2, '0');
    function formatDateToUTC(date) {
        return (
            date.getUTCFullYear() +
            padTo2Digits(date.getUTCMonth() + 1) +
            padTo2Digits(date.getUTCDate()) +
            'T' +
            padTo2Digits(date.getUTCHours()) +
            padTo2Digits(date.getUTCMinutes()) +
            padTo2Digits(date.getUTCSeconds()) +
    const formattedDate = formatDateToUTC(expirationDate);
    // 生成x-oss-credential并设置表单数据
    const credential = getCredential(formattedDate.split('T')[0], getStandardRegion(client.options.region), client.options.accessKeyId);
    formData.set('x_oss_date', formattedDate);
    formData.set('x_oss_credential', credential);
    formData.set('x_oss_signature_version', 'OSS4-HMAC-SHA256');
    // 创建policy
    // 示例policy表单域只列举必填字段
    const policy = {
        expiration: expirationDate.toISOString(),
        conditions: [
            { 'bucket': 'examplebucket'}, 
            { 'x-oss-credential': credential },
            { 'x-oss-signature-version': 'OSS4-HMAC-SHA256' },
            { 'x-oss-date': formattedDate },
    // 如果存在STS Token,添加到策略和表单数据中
    if (client.options.stsToken) {
        policy.conditions.push({ 'x-oss-security-token': client.options.stsToken });
        formData.set('security_token', client.options.stsToken);
    // 生成签名并设置表单数据
    const signature = client.signPostObjectPolicyV4(policy, date);
    formData.set('policy', Buffer.from(policy2Str(policy), 'utf8').toString('base64'));
    formData.set('signature', signature);
    // 返回表单数据
    return {
        host: `http://${client.options.bucket}.oss-${client.options.region}.aliyuncs.com`, 
        policy: Buffer.from(policy2Str(policy), 'utf8').toString('base64'),
        x_oss_signature_version: 'OSS4-HMAC-SHA256',
        x_oss_credential: credential,
        x_oss_date: formattedDate,
        signature: signature,
        dir: 'user-dir', // 指定上传到OSS的文件前缀
        security_token: client.options.stsToken
app.get('/get_post_signature_for_oss_upload', async (req, res) => {
    try {
        const result = await GenerateSignature();
        res.json(result); // 返回生成的签名数据
    } catch (error) {
        console.error('Error generating signature:', error);
        res.status(500).send('Error generating signature');
app.listen(PORT, () => {
    console.log(`Server is running on http://127.0.0.1:${PORT}`); // 如果需要监听其他地址如0.0.0.0,需要您自行在服务端添加认证机制
});

Go

执行以下命令安装依赖。

go get -u github.com/aliyun/credentials-go
go mod tidy

请参考如下代码来完成服务端获取临时访问凭证 STStoken 并构建上传策略以计算 POST 签名。

package main
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "hash"
    "log"
    "net/http"
    "time"
    "github.com/aliyun/credentials-go/credentials"
// 定义全局变量
var (
    region     string
    bucketName string
    product    = "oss"
// PolicyToken 结构体用于存储生成的表单数据
type PolicyToken struct {
    Policy           string `json:"policy"`
    SecurityToken    string `json:"security_token"`
    SignatureVersion string `json:"x_oss_signature_version"`
    Credential       string `json:"x_oss_credential"`
    Date             string `json:"x_oss_date"`
    Signature        string `json:"signature"`
    Host             string `json:"host"` 
    Dir              string `json:"dir"` 
func main() {
    // 定义默认的IP和端口字符串
    strIPPort := ":8000"
    if len(os.Args) == 3 {
    strIPPort = fmt.Sprintf("%s:%s", os.Args[1], os.Args[2])
    } else if len(os.Args) != 1 {
    fmt.Println("Usage   : go run test1.go                ")
    fmt.Println("Usage   : go run test1.go ip port        ")
    fmt.Println("")
    os.Exit(0)
    // 打印服务器运行的地址和端口
    fmt.Printf("server is running on %s \n", strIPPort)
    // 注册处理根路径请求的函数
    http.HandleFunc("/", handlerRequest)
    // 注册处理获取签名请求的函数
    http.HandleFunc("/get_post_signature_for_oss_upload", handleGetPostSignature)
    // 启动HTTP服务器
    err := http.ListenAndServe(strIPPort, nil)
    if err != nil {
    strError := fmt.Sprintf("http.ListenAndServe failed : %s \n", err.Error())
    panic(strError)
// handlerRequest 函数处理根路径请求
func handlerRequest(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
    http.ServeFile(w, r, "templates/index.html")
    return
    http.NotFound(w, r)
// handleGetPostSignature 函数处理获取签名请求
func handleGetPostSignature(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
    response := getPolicyToken()
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*") // 允许跨域
    w.Write([]byte(response))
    return
    http.NotFound(w, r)
// getPolicyToken 函数生成 OSS 上传所需的签名和凭证
func getPolicyToken() string {
    // 设置bucket所处地域
    region = "cn-hangzhou"
    // 替换为您的bucket名称
    bucketName = "examplebucket"
    // 设置 OSS 上传地址
    host := fmt.Sprintf("https://%s.oss-%s.aliyuncs.com", bucketName, region)
    // 设置上传目录
    dir := "user-dir"
    config := new(credentials.Config).
    SetType("ram_role_arn").
    SetAccessKeyId(os.Getenv("OSS_ACCESS_KEY_ID")).
    SetAccessKeySecret(os.Getenv("OSS_ACCESS_KEY_SECRET")).
    SetRoleArn("OSS_STS_ROLE_ARN").
    SetRoleSessionName("Role_Session_Name").
    SetPolicy("").
    SetRoleSessionExpiration(3600)
    // 根据配置创建凭证提供器
    provider, err := credentials.NewCredential(config)
    if err != nil {
    log.Fatalf("NewCredential fail, err:%v", err)
    // 从凭证提供器获取凭证
    cred, err := provider.GetCredential()
    if err != nil {
    log.Fatalf("GetCredential fail, err:%v", err)
    // 构建policy
    utcTime := time.Now().UTC()
    date := utcTime.Format("20060102")
    expiration := utcTime.Add(1 * time.Hour)
    policyMap := map[string]any{
    "expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
    "conditions": []any{
    map[string]string{"bucket": bucketName},
    map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"},
    map[string]string{"x-oss-credential": fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request", *cred.AccessKeyId, date, region, product)},
    map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")},
    map[string]string{"x-oss-security-token": *cred.SecurityToken},
    // 将policy转换为 JSON 格式
    policy, err := json.Marshal(policyMap)
    if err != nil {
    log.Fatalf("json.Marshal fail, err:%v", err)
    // 构造待签名字符串(StringToSign)
    stringToSign := base64.StdEncoding.EncodeToString([]byte(policy))
    hmacHash := func() hash.Hash { return sha256.New() }
        // 构建signing key
    signingKey := "aliyun_v4" + *cred.AccessKeySecret
    h1 := hmac.New(hmacHash, []byte(signingKey))
    io.WriteString(h1, date)
    h1Key := h1.Sum(nil)
    h2 := hmac.New(hmacHash, h1Key)
    io.WriteString(h2, region)
    h2Key := h2.Sum(nil)
    h3 := hmac.New(hmacHash, h2Key)
    io.WriteString(h3, product)
    h3Key := h3.Sum(nil)
    h4 := hmac.New(hmacHash, h3Key)
    io.WriteString(h4, "aliyun_v4_request")
    h4Key := h4.Sum(nil)
    // 生成签名
    h := hmac.New(hmacHash, h4Key)
    io.WriteString(h, stringToSign)
    signature := hex.EncodeToString(h.Sum(nil))
    // 构建返回给前端的表单
    policyToken := PolicyToken{
    Policy:           stringToSign,
    SecurityToken:    *cred.SecurityToken,
    SignatureVersion: "OSS4-HMAC-SHA256",
    Credential:       fmt.Sprintf("%v/%v/%v/%v/aliyun_v4_request", *cred.AccessKeyId, date, region, product),
    Date:             utcTime.UTC().Format("20060102T150405Z"),
    Signature:        signature,
    Host:             host, // 返回 OSS 上传地址
    Dir:              dir,  // 返回上传目录
    response, err := json.Marshal(policyToken)
    if err != nil {
    fmt.Println("json err:", err)
    return string(response)
}

PHP

执行以下命令安装依赖。

composer install

请参考如下代码来完成服务端获取临时访问凭证 STStoken 并构建上传策略以计算 POST 签名。

<?php
// 引入阿里云SDK
require_once __DIR__ . '/vendor/autoload.php';
use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
use AlibabaCloud\Sts\Sts;
// 禁用错误显示
ini_set('display_errors', '0');
$bucket = 'examplebucket'; // 替换为您的Bucket名称
$region_id = 'cn-hangzhou'; // 替换为您的Bucket所在地域
$host = 'http://examplebucket.oss-cn-hangzhou.aliyuncs.com'; // 替换为您的Bucket域名
$expire_time = 3600; // 过期时间,单位为秒
$upload_dir = 'user-dir'; // 上传文件的前缀
// 计算HMAC-SHA256
function hmacsha256($key, $data) {
    return hash_hmac('sha256', $data, $key, true);
// 处理获取POST签名的请求
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/get_post_signature_for_oss_upload') {
    AlibabaCloud::accessKeyClient(getenv('OSS_ACCESS_KEY_ID'), getenv('OSS_ACCESS_KEY_SECRET'))
        ->regionId('cn-hangzhou')
        ->asDefaultClient();
    // 创建STS请求。
    $request = Sts::v20150401()->assumeRole();
    // 发起STS请求并获取结果。
    // 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称,例如oss-role-session。
    // 将<YOUR_ROLE_ARN>替换为拥有上传文件到指定OSS Bucket权限的RAM角色的ARN。
    $result = $request
        ->withRoleSessionName('oss-role-session')
        ->withDurationSeconds(3600)
        ->withRoleArn(getenv('OSS_STS_ROLE_ARN'))
        ->request();
    // 获取STS请求结果中的凭证信息。
    $tokenData = $result->get('Credentials');
    // 构建返回的JSON数据。
    $tempAccessKeyId = $tokenData['AccessKeyId'];
    $tempAccessKeySecret = $tokenData['AccessKeySecret'];
    $securityToken = $tokenData['SecurityToken'];
    $now = time();
    $dtObj = gmdate('Ymd\THis\Z', $now);
    $dtObj1 = gmdate('Ymd', $now);
    $dtObjPlus3h = gmdate('Y-m-d\TH:i:s.u\Z', strtotime('+3 hours', $now));
    // 构建Policy
    $policy = [
        "expiration" => $dtObjPlus3h,
        "conditions" => [
            ["x-oss-signature-version" => "OSS4-HMAC-SHA256"],
            ["x-oss-credential" => "{$tempAccessKeyId}/{$dtObj1}/cn-hangzhou/oss/aliyun_v4_request"],
            ["x-oss-security-token" => $securityToken],
            ["x-oss-date" => $dtObj],
    $policyStr = json_encode($policy);
    // 构造待签名字符串
    $stringToSign = base64_encode($policyStr);
    // 计算SigningKey
    $dateKey = hmacsha256(('aliyun_v4' . $tempAccessKeySecret), $dtObj1);
    $dateRegionKey = hmacsha256($dateKey, 'cn-hangzhou');
    $dateRegionServiceKey = hmacsha256($dateRegionKey, 'oss');
    $signingKey = hmacsha256($dateRegionServiceKey, 'aliyun_v4_request');
    // 计算Signature
    $result = hmacsha256($signingKey, $stringToSign);
    $signature = bin2hex($result);
    // 返回签名数据
    $responseData = [
        'policy' => $stringToSign,
        'x_oss_signature_version' => "OSS4-HMAC-SHA256",
        'x_oss_credential' => "{$tempAccessKeyId}/{$dtObj1}/cn-hangzhou/oss/aliyun_v4_request",
        'x_oss_date' => $dtObj,
        'signature' => $signature,
        'host' => $host,
        'dir' => $upload_dir,
        'security_token' => $securityToken
    header('Content-Type: application/json');
    echo json_encode($responseData);
    exit;
// 首页路由
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/') {
    echo file_get_contents(__DIR__ . '/public/index.html');
    exit;
// 其他路由
http_response_code(404);
echo json_encode(['message' => 'Not Found']);
exit;
?>

步骤三:配置 Web

Web 端构造并提交表单上传请求

Web 端从服务端接收到所有必需的信息后,就可以构建 HTML 表单请求了。此请求将直接与 OSS 服务进行通信,从而实现文件的上传。

Web 接收到的响应示例

业务服务器向 Web 端返回 STS Token 和上传策略。

<!DOCTYPE html>
<html lang="zh-CN">
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>服务端生成签名上传文件到OSS</title>
</head>
<div class="container">
        <div class="mb-3">
            <label for="file" class="form-label">选择文件:</label>
            <input type="file" class="form-control" id="file" name="file" required />
        <button type="submit" class="btn btn-primary">上传</button>
    </form>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    const form = document.querySelector("form");
    const fileInput = document.querySelector("#file");
    form.addEventListener("submit", (event) => {
        event.preventDefault();
        const file = fileInput.files[0];
        if (!file) {
            alert('请选择一个文件再上传。');
            return;
        const filename = file.name;
        fetch("/get_post_signature_for_oss_upload", { method: "GET" })
            .then((response) => {
                if (!response.ok) {
                    throw new Error("获取签名失败");
                return response.json();
            .then((data) => {
                let formData = new FormData();
                formData.append("success_action_status", "200");
                formData.append("policy", data.policy);
                formData.append("x-oss-signature", data.signature);
                formData.append("x-oss-signature-version", "OSS4-HMAC-SHA256");
                formData.append("x-oss-credential", data.x_oss_credential);
                formData.append("x-oss-date", data.x_oss_date);
                formData.append("key", data.dir + file.name); // 文件名
                formData.append("x-oss-security-token", data.security_token);
                formData.append("file", 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>
</body>
</html>
{
    "dir": "user-dirs",
    "host": "http://examplebucket.oss-cn-hangzhou.aliyuncs.com",
    "policy": "eyJl****",
    "security_token": "CAIS****",
    "signature": "9103****",
    "x_oss_credential": "STS.NSpW****/20241127/cn-hangzhou/oss/aliyun_v4_request",
    "x_oss_date": "20241127T060941Z",
    "x_oss_signature_version": "OSS4-HMAC-SHA256"
}

Body 中的各字段说明如下:

字段

描述

dir

限制上传的文件前缀。

host

Bucket 域名。

policy

用户表单上传的策略(Policy),详情请参见 Post Policy

security_token

安全令牌。

signature

Policy 签名后的字符串。详情请参见 Post Signature

x_oss_credential

指明派生密钥的参数集。

x_oss_date

请求的时间,其格式遵循 ISO 8601 日期和时间标准,例如 20231203T121212Z

x_oss_signature_version

指定签名的版本和算法,固定值为 OSS4-HMAC-SHA256。

  • 表单请求中包含文件内容和服务器返回的参数。

  • 通过这个请求,Web 端可以直接与阿里云的 OSS 进行通信,完成文件上传。

说明
  • file 表单域外,包含 key 在内的其他所有表单域的大小均不能超过 8 KB。

  • Web 端上传默认同名覆盖,如果您不希望覆盖同名文件,可以在上传请求的 header 中携带参数 x-oss-forbid-overwrite,并指定其值为 true。当您上传的文件在 OSS 中存在同名文件时,该文件会上传失败,并返回 FileAlreadyExists 错误。

<!DOCTYPE html>
<html lang="zh-CN">
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>服务端生成签名上传文件到OSS</title>
</head>
<div class="container">
        <div class="mb-3">
            <label for="file" class="form-label">选择文件:</label>
            <input type="file" class="form-control" id="file" name="file" required />
        <button type="submit" class="btn btn-primary">上传</button>
    </form>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    const form = document.querySelector("form");
    const fileInput = document.querySelector("#file");
    form.addEventListener("submit", (event) => {
        event.preventDefault();
        const file = fileInput.files[0];
        if (!file) {
            alert('请选择一个文件再上传。');
            return;
        const filename = file.name;
        fetch("/get_post_signature_for_oss_upload", { method: "GET" })
            .then((response) => {
                if (!response.ok) {
                    throw new Error("获取签名失败");
                return response.json();
            .then((data) => {
                let formData = new FormData();
                formData.append("success_action_status", "200");
                formData.append("policy", data.policy);
                formData.append("x-oss-signature", data.signature);
                formData.append("x-oss-signature-version", "OSS4-HMAC-SHA256");
                formData.append("x-oss-credential", data.x_oss_credential);
                formData.append("x-oss-date", data.x_oss_date);
                formData.append("key", data.dir + file.name); // 文件名
                formData.append("x-oss-security-token", data.security_token);
                formData.append("file", 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>
</body>
</html>
  • HTML 表单包含一个文件输入框和一个提交按钮,用户可以选择要上传的文件并提交表单。

  • 当表单提交时,JavaScript 代码会阻止默认的表单提交行为,然后通过 AJAX 请求从服务器获取上传所需的签名信息。

  • 获取到签名信息后,构造一个 FormData 对象,包含所有必要的表单字段。

  • 通过 fetch 方法发送 POST 请求到 OSS 服务的 URL,完成文件上传。

如果上传成功,显示“文件已上传”的提示;如果上传失败,显示相应的错误信息。

结果验证

以上步骤部署完成后,您可以访问服务器地址,体验 Web 端签名直传功能。

  1. 通过浏览器访问服务端地址,然后点击上传按钮选择上传的文件。效果示例如下:

    2024-11-08_14-42-06 (1) copy

  2. Bucket 列表 页面,选择您之前创建的用来存放用户上传文件的 Bucket 并打开,您可以在上传列表中看到您通过 Web 端上传的文件。

    image

建议配置

限制文件上传的权限和约束条件

您可以根据实际需求修改代码中的 policy ,以设置通过 HTML 表单上传文件到 OSS 时的权限限制和约束条件。policy 表单域采用 JSON 格式定义,通过设置不同的参数来限制上传操作,确保上传过程的安全性和合规性,例如允许上传的 Bucket 名称、Object 前缀、有效期、允许的 HTTP 方法、上传内容的大小限制、内容类型限制等。

{
  "expiration": "2023-12-03T13:00:00.000Z",
  "conditions": [
    {"bucket": "examplebucket"},
    {"x-oss-signature-version": "OSS4-HMAC-SHA256"},
    {"x-oss-credential": "AKIDEXAMPLE/20231203/cn-hangzhou/oss/aliyun_v4_request"},
    {"x-oss-security-token": "CAIS******"},
    {"x-oss-date": "20231203T121212Z"},
    ["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"]]
}

policy 详细说明如下:

字段

类型

是否必选

描述

Conditions 匹配方式

content-length-range

字符串

上传 Object 的最小和最大允许大小,单位为字节。

content-length-range

success_action_status

字符串

上传成功后的返回状态码。

eq、starts-with、in not-in

key

字符串

上传的 Object 名称。

eq、starts-with、in not-in

content-type

字符串

限制上传的文件类型。

eq、starts-with、in not-in

cache-control

字符串

指定 Object 的缓存行为。

eq、starts-with、in not-in

关于 Policy 参数的更多说明请参见 Post Policy

服务端签名直传并设置上传回调

如果您需要获取更多关于用户上传文件的信息,例如文件名称、图片大小等,请使用上传回调方案。通过设置上传回调,您可以在用户上传文件后,自动接收到相关文件信息。关于如何配置服务端签名直传并设置上传回调,请参见 服务器端签名直传并设置上传回调

配置 CORS 规则时将来源设为服务器地址

在之前的操作步骤中,为了简化流程,将允许的跨域请求来源设置为通配符 * 。然而,出于安全考虑,建议您对来源进行更严格的限制。为此,您可以将创建的 OSS Bucket 的跨域资源共享来源参数设置为您业务服务器的具体地址。这样一来,唯有来自您指定服务器的请求才能被授权执行跨域操作,有效增强了系统的安全性。

参数

示例值

来源

http://业务服务器地址

允许 Methods

POST、PUT、GET

允许 Headers

*

清理资源

在本方案中,您创建了 1 ECS 实例、1 OSS Bucket、1 RAM 用户和 1 RAM 角色。测试完方案后,您可以参考以下规则处理对应产品的资源,避免继续产生费用或产生安全风险。

释放 ECS 实例

如果您不再需要这台实例,可以将其释放。释放后,实例停止计费,数据不可恢复。具体操作如下:

  1. 返回 云服务器 ECS 控制台 实例列表 页面,根据地域、实例 ID 找到目标 ECS 实例,单击 操作 列下的 image

  2. 选择 释放 image

  3. 确认实例无误后,选择 立即释放 ,单击 下一步

  4. 确认即将释放的关联资源,并了解相关数据风险后,单击 确认 ,即可完成 ECS 实例的释放。

说明
  • 系统盘、分配的公网 IP 将随实例释放。

  • 安全组、交换机和 VPC 不会随实例释放,但它们均为免费资源,您可根据实际的业务需要选择性删除。

  • 弹性公网 IP 不会随实例释放,且不是免费资源,您可根据实际的业务需要选择性删除。

删除 Bucket

  1. 登录 OSS 管理控制台

  2. 单击 Bucket 列表 ,然后单击目标 Bucket 名称。

  3. 删除 Bucket 的所有文件(Object)。

  4. 在左侧导航栏,单击 删除 Bucket ,然后按照页面指引完成删除操作。

删除 RAM 用户

  1. 使用 RAM 管理员登录 RAM 控制台

  2. 在左侧导航栏,选择 身份管理 > 用户

  3. 用户 页面,单击目标 RAM 用户 操作 列的 删除

    您也可以选中多个 RAM 用户,然后单击用户列表下方的 删除用户 ,批量将多个 RAM 用户移入回收站。

  4. 删除用户 对话框,仔细阅读删除影响,然后输入目标 RAM 用户名称,最后单击 移入回收站

删除 RAM 角色

  1. 使用 RAM 管理员登录 RAM 控制台

  2. 在左侧导航栏,选择 身份管理 > 角色

  3. 角色 页面,单击目标 RAM 角色 操作 列的 删除角色

  4. 删除角色 对话框,输入 RAM 角色名称,然后单击 删除角色

    说明

    如果 RAM 角色被授予了权限策略,删除角色时,会同时解除授权。

常见问题

是否可以支持分片上传大文件、断点续传?

此方案是使用 HTML 表单上传的方式上传文件,不支持基于分片上传大文件和基于分片断点续传的场景。如果您想实现分片上传大文件或断点续传,请参考 服务端生成 STS 临时访问凭证

如何防止上传的文件被覆盖

如果希望防止文件覆盖,可以在上传请求的 Header 中添加 x-oss-forbid-overwrite 参数,并将其值设为 true 。例如:

formData.append('x-oss-forbid-overwrite', 'true');