服务端签名直传

更新时间:
复制为 MD 格式
一键部署
我的部署

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

方案概览

image

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

说明

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

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

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

  3. 配置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(杭州)为例。

      付费类型中选择按量付费(先使用后付费,按需开通)。按量付费实例不支持备案服务。实例创建之后地域将无法更改,不同地域的实例之间内网互不相通。

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

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

    说明

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

    image

    在创建专有网络页面,设置名称为 ProjectA-VPC,IPv4 网段选择 手动输入 IPv4 地址段 并填写 192.168.0.0/16,IPv6 网段选择 不分配

    交换机配置示例:名称设置为 ProjectA-vSwitch,可用区选择 杭州 可用区H,IPv4 网段设置为 192.168.10.0/24,单击确定完成创建。

  1. 选择规格 & 镜像

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

    image

  1. 选择存储

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

    系统盘选择ESSD云盘,性能级别为PL0,容量设置为40 GiB,勾选随实例释放,不添加数据盘。

  1. 绑定公网IP

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

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

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

    带宽峰值设置为 5 Mbps。

  1. 创建安全组

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

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

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

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

    安全组类型区域选择普通安全组

  1. 创建密钥对

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

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

      说明

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

      登录凭证区域选择密钥对页签,单击创建密钥对完成密钥对创建,然后在密钥对下拉框中选择已创建的密钥对(例如 ecs-test)。

  1. 创建并查看ECS实例

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

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

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

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

操作二:连接ECS实例

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

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

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

  4. 说明

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

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

    Welcome to Alibaba Cloud Elastic Compute Service !
    Last login: Sun Sep 29 15:35:22 2024 from xxx
    [ecs-user@iz*** ~]$

一、配置用户权限

说明

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

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

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

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

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

  3. 单击创建用户

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

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

重要

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

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

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

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

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

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

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

    说明

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

  4. 单击确认新增授权

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

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

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

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

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

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

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

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

按照最小授权原则,为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. 新增授权页面下,选择自定义策略,选择已创建的自定义权限策略。

  4. 单击确定

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

说明

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

您可以参照以下步骤添加环境变量。

Linux系统

  1. 在命令行界面执行以下命令来将环境变量设置追加到~/.bashrc文件中。

    echo "export OSS_ACCESS_KEY_ID='your-access-key-id'" >> ~/.bashrc
    echo "export OSS_ACCESS_KEY_SECRET='your-access-key-secret'" >> ~/.bashrc
    echo "export OSS_STS_ROLE_ARN='your-role-arn'" >> ~/.bashrc
  2. 执行以下命令使变更生效。

    source  ~/.bashrc
  3. 执行以下命令检查环境变量是否生效。

    echo $OSS_ACCESS_KEY_ID
    echo $OSS_ACCESS_KEY_SECRET
    echo $OSS_STS_ROLE_ARN

macOS系统

  1. 在终端中执行以下命令,查看默认Shell类型。

    echo $SHELL
  2. 根据默认Shell类型进行操作。

    Zsh

    1. 执行以下命令来将环境变量设置追加到 ~/.zshrc 文件中。

      echo "export OSS_ACCESS_KEY_ID='your-access-key-id'" >> ~/.zshrc
      echo "export OSS_ACCESS_KEY_SECRET='your-access-key-secret'" >> ~/.zshrc
      echo "export OSS_STS_ROLE_ARN='your-role-arn'" >> ~/.zshrc
    2. 执行以下命令使变更生效。

      source ~/.zshrc
    3. 执行以下命令检查环境变量是否生效。

      echo $OSS_ACCESS_KEY_ID
      echo $OSS_ACCESS_KEY_SECRET
      echo $OSS_STS_ROLE_ARN

    Bash

    1. 执行以下命令来将环境变量设置追加到 ~/.bash_profile 文件中。

      echo "export OSS_ACCESS_KEY_ID='your-access-key-id'" >> ~/.bash_profile
      echo "export OSS_ACCESS_KEY_SECRET='your-access-key-secret'" >> ~/.bash_profile
      echo "export OSS_STS_ROLE_ARN='your-role-arn'" >> ~/.bash_profile
    2. 执行以下命令使变更生效。

      source ~/.bash_profile
    3. 执行以下命令检查环境变量是否生效。

      echo $OSS_ACCESS_KEY_ID
      echo $OSS_ACCESS_KEY_SECRET
      echo $OSS_STS_ROLE_ARN

Windows系统

  1. CMD中运行以下命令。

    setx OSS_ACCESS_KEY_ID "your-access-key-id"
    setx OSS_ACCESS_KEY_SECRET "your-access-key-secret"
    setx 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.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);
        // 定义时区为UTC
        ZoneId zone = ZoneOffset.UTC;  
        // 将 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临时凭证
     * @return AssumeRoleResponseBodyCredentials 对象
     */
    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);
        }
        // 返回一个默认的错误响应对象,避免返回null
        AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials defaultCredentials = new AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials();
        defaultCredentials.accessKeyId = "ERROR_ACCESS_KEY_ID";
        defaultCredentials.accessKeySecret = "ERROR_ACCESS_KEY_SECRET";
        defaultCredentials.securityToken = "ERROR_SECURITY_TOKEN";
        return defaultCredentials;
    }
    @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
        ZonedDateTime today = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
        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");
        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_SECRET, 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哈希值,字节类型
    """
    try:
        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()) +
            'Z'
        );
    }
    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"
    "io"
    "hash"
    "log"
    "net/http"
    "os"
    "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和上传策略。

{
    "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">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>服务端生成签名上传文件到OSS</title>
</head>
<body>
<div class="container">
    <form>
        <div class="mb-3">
            <label for="file" class="form-label">选择文件:</label>
            <input type="file" class="form-control" id="file" name="file" required />
        </div>
        <button type="submit" class="btn btn-primary">上传</button>
    </form>
</div>
<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. 通过浏览器访问服务端地址,点击选择文件按钮,选择文件后上传。

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

建议配置

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

您可以根据实际需求修改代码中的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/jpeg", "image/png"]],
    ["not-in", "$cache-control", ["no-cache"]]
  ]
}

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

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

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

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

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

参数

示例值

来源

http://业务服务器地址

允许 Methods

POST、PUT、GET

允许 Headers

*

清理资源

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

释放ECS实例

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

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

  2. 选择释放。在弹出的菜单中,单击释放

  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表单上传的方式上传文件,不支持基于分片上传大文件和基于分片断点续传的场景。如果您想实现分片上传大文件或断点续传,请参考断点续传上传分片上传

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

如果希望防止文件覆盖,可以在上传请求的表单字段携带x-oss-forbid-overwrite参数,并将其值设为true。例如:

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