文档

服务端签名直传

更新时间:

对于需要限制上传文件属性的场景,您可以在服务端生成PostObject所需的Post签名、PostPolicy等信息,然后客户端可以凭借这些信息,在一定的限制下不依赖OSS SDK直接上传文件。您可以借助服务端生成的PostPolicy限制客户端上传的文件,例如限制文件大小、文件类型。此方案适用于通过HTML表单上传的方式上传文件。需要注意的是,此方案不支持基于分片上传大文件、基于分片断点续传的场景。

请求流程

以下以集成STS完成服务端签名直传为例进行说明。集成STS后,客户端能够在不直接接触长期凭证的情况下,利用STS Token完成表单上传,增强上传过程的安全性。

image
  1. 客户端向业务服务器请求上传策略。

    客户端首先向业务服务器发送请求,请求中应包含所需上传操作的最小权限需求(例如上传到特定Bucket的权限)和期望的有效时间等信息。不同于直接请求Post签名和Post Policy,客户端需要请求的是一个带有特定权限限制的STS Token以及上传策略。

  2. 业务服务器向STS服务请求获取STS Token。

    业务服务器收到客户端请求后,使用长期凭证向阿里云STS服务发起请求,生成一个具有限定权限和有效时间的STS Token。

  3. STS服务向业务服务器返回STS Token。

  4. 业务服务器根据STS Token和其他上传限制生成上传策略。

    业务服务器根据返回的STS Token和其他上传限制条件(例如Bucket名称、目录路径、过期时间等)生成一个安全的上传策略(Policy),并返回给客户端。

  5. 客户端构造并提交表单上传请求。

    客户端使用收到的STS Token和上传策略,结合文件信息构造HTML表单。此时,上传策略中的签名和STS Token的加入使得客户端可以直接与OSS交互,而无需暴露业务服务器的长期凭证。

  6. OSS返回成功响应给客户端。

前提条件

  • 已创建Bucket。具体步骤,请参见创建Bucket

  • 为Bucket设置跨域规则,且跨域规则中允许Methods选项需选中POST,否则无法执行表单上传。具体步骤,请参见跨域设置

权限说明

要使用服务端签名直传时,您必须拥有oss:PutObject权限。具体操作,请参见为RAM用户授权

方案部署

本方案部署的示例工程:server-signed-direct-upload.zip

手动部署

准备工作

  1. 创建RAM用户。

    1. 登录RAM控制台

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

    3. 单击创建用户

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

    5. 访问方式区域下,选择OpenAPI调用访问,然后单击确定

    6. 根据界面提示,完成安全验证。

    7. 复制访问密钥(AccessKey ID和AccessKey Secret)。

      重要

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

  2. 为RAM用户授予请求AssumeRole的权限。

    创建RAM用户后,您需要授予RAM用户通过扮演角色来调用STS服务的权限。

    1. 单击已创建RAM用户右侧对应的添加权限

    2. 添加权限页面,选择AliyunSTSAssumeRoleAccess系统策略。

      说明

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

      image.png

    3. 单击确定

  3. 创建RAM角色。

    您需要创建RAM角色,用于定义RAM角色被扮演时,可以获得OSS服务的哪些访问权限。

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

    2. 单击创建角色,选择可信实体类型为阿里云账号,单击下一步

    3. 创建角色对话框,角色名称填写为RamOssTest,选择信任的云账号当前云账号

    4. 单击完成。角色创建完成后,单击关闭

    5. 角色页面,搜索框输入角色名称RamOssTest,然后单击RamOssTest

    6. 单击ARN右侧的复制,保存角色的ARN。arn

  4. 为RAM角色授予上传文件的权限。

    为RAM角色附加权限策略,明确RAM角色在被扮演时所能拥有的OSS资源访问权限。结合本实例教程,希望RAM用户在扮演该角色后只能向OSS指定Bucket上传文件,则需要为角色添加写入权限的策略。

    1. 创建上传文件的自定义权限策略。

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

      2. 权限策略页面,单击创建权限策略

      3. 创建权限策略页面,单击脚本编辑,然后在策略文档输入框中赋予角色上传文件到examplebucket的权限。具体配置示例如下。

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

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

    2. 为RAM角色RamOssTest授予自定义权限策略。

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

      2. 角色页面,找到目标RAM角色RamOssTest

      3. 单击RAM角色RamOssTest右侧的新增授权

      4. 添加权限页面下的自定义策略页签,选择已创建的自定义权限策略RamTestPolicy

      5. 单击确定

操作步骤

  1. 修改示例工程源码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",
    };
  2. 业务服务器请求获取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系统
    }
    
  3. 业务服务器向客户端返回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

    限制上传的文件前缀。

  4. 客户端构造并提交表单上传请求。

    说明
    • 除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>
    

快速部署

您可以使用资源编排ROS一键配置1个云服务器ECS实例和1个对象存储OSS Bucket,并在云服务器ECS实例上部署应用服务器和客户端,从而快速体验服务端签名直传。使用资源编排ROS快速体验服务端签名直传的操作步骤如下。

  1. 一键部署云资源。

    1. 打开一键配置模板链接

    2. 资源编排 ROS控制台,输入资源栈名称,输入新建OSS Bucket的名称,设置新购ECS的可用区、实例类型、系统盘类型、实例密码,然后单击创建

      资源栈的资源栈信息页签下状态显示创建中

    3. 资源栈的状态显示创建成功后,单击输出页签,查看一键部署的云服务器ECS实例、OSS Bucket。

  2. 体验服务端签名直传。

    1. 输出页签下,复制OssClientAddress的值,然后在浏览器中打开。

    2. OSS web直传页面,单击选择文件,选择指定类型的文件,然后单击开始上传

  3. 体验完成后,释放测试资源,避免继续产生费用。

    1. 资源栈页面的右上角,单击删除

    2. 删除资源栈页面,确保删除方式释放资源,然后单击确定

相关文档

大多数情况下,应用服务器需要了解用户上传了哪些文件以及对应的文件名称等信息。如果上传的是图片,还希望获取图片大小等。此时,您可以通过上传回调方案实现该需求。关于服务端签名直传并设置上传回调的介绍,请参见服务器端签名直传并设置上传回调

  • 本页导读 (1)