文档

客户端签名直传

更新时间:

客户端签名直传是指从服务端获取sts token,并使用sts token生成签名,最后使用此签名上传文件到OSS。客户端可以直接将文件上传至OSS,减少了中间环节,因此可以加快上传速度。本文介绍如何进行客户端签名直传。

安全风险

为了减少企业的业务服务器负担并提高效率,该Web应用被设计为在用户的网页浏览器中直接与OSS交互,而非所有请求都通过企业的业务服务器中转。

企业A计划采取以下方案搭建Web应用文件直传服务:

image

由于网页浏览器运行环境完全处于用户端且不受企业A直接控制,企业A面临以下挑战:

  • 密钥泄露的重大风险:网页浏览器归用户所有,并且被视为不可信的环境。若在浏览器中存储RAM用户的访问密钥等敏感信息,则可能会面临密钥意外泄露的显著风险。一旦密钥落入不法分子之手,他们就可能利用这些密钥非法访问企业在云端的敏感数据,导致核心商业机密、客户隐私信息、知识产权及其他关键资产的泄漏,给企业造成不可估量的经济损失和信誉损害。

  • 过度权限的安全漏洞:在为企业的业务服务器应用程序创建RAM用户时,往往需要赋予该用户较高的权限,以便于它可以访问和管理其他云服务。然而,如果用户的访问密钥被存储在浏览器中而后又意外泄露,这将可能导致拥有过多权限的密钥落入恶意攻击者之手。此举不仅增加了企业资源被滥用的概率,还可能带来更严重的问题。黑客可能会使用盗取的密钥在企业的云资源上部署病毒或恶意软件,比如植入后门、植入密码窃取木马程序、以企业资源进行加密货币挖矿等,严重破坏企业的正常业务运营,并对企业的财务和声誉构成严重威胁。

解决方案

为了应对上述风险,企业A可以在原有方案的基础上增加临时授权。通过这种方式,企业A能够在确保数据直传效率的同时,实现以下效果:

  • 增强的身份验证和授权:通过STS生成的具有时间限制的令牌,即便在短时间内泄露,也极大降低了安全风险。因为这些凭证很快就会失效,降低了被不当利用的可能性。

  • 精细化权限控制:STS允许根据最小权限原则配置权限,仅授权Web应用必需的访问权限。这种精细化的权限控制方法限制了潜在泄露的影响范围,防止了过度权限的风险。

企业A最终采取以下方案搭建Web应用文件直传服务:

image

方案部署

下面将以一个简单的用户文件上传场景为例,引导您一步步使用OSS和STS为Web应用部署浏览器文件直传服务。

本方案部署的示例工程:oss-upload-sts.zip

一键部署

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

  1. 一键部署云资源。

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

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

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

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

  2. 体验客户端签名直传。

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

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

完成与清理

方案验证

完成以上操作后,您可以查看文件是否已上传到OSS。

  1. 登录对象存储控制台

  2. 在左侧导航栏,选择Bucket列表

  3. Bucket列表页面,单击目标Bucket。

  4. 文件列表页面,查看成功上传的文件。

    1.png

清理资源

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

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

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

手动部署

准备工作

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

    参数

    示例值

    所属地域

    华东1(杭州)

    Bucket名称

    web-direct-upload

    具体步骤,请参见创建存储空间

  • 创建一台ECS实例作为业务服务器,用于生成临时身份凭证。

    说明

    在实际部署时,您可以将调用STS服务的接口集成到自己的业务服务器的接口中,而无需创建该ECS实例。

    参数

    示例值

    付费类型

    按量付费

    地域

    华东1(杭州)

    公网 IP

    分配公网 IPv4 地址

    安全组

    开放HTTP (TCP:80)端口

    具体步骤,请参见通过控制台使用ECS实例(快捷版)

  • 为创建的OSS Bucket配置跨域资源共享。

    参数

    示例值

    来源

    http://ECS公网IP地址

    允许Methods

    PUT

    允许Headers

    *

    具体步骤,请参见跨域设置

部署步骤

步骤一:在访问控制创建RAM用户

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

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

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

  3. 单击创建用户

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

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

  6. 单击操作下的复制,保存调用密钥(AccessKey ID和AccessKey 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角色管理页面,搜索框输入角色名称,例如oss-web-upload

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

    1.png

步骤四:在访问控制创建自定义权限策略

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

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

  2. 单击创建权限策略

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

    重要

    以下示例仅供参考。您需要根据实际需求配置更细粒度的授权策略,防止出现权限过大的风险。关于更细粒度的授权策略配置详情,请参见对象存储自定义权限策略参考

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

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

步骤五:在访问控制为RAM角色授予权限

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

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

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

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

  4. 单击确定

步骤六:在业务服务器获取临时身份凭证

在Web应用中,通过在业务服务器集成STS SDK,实现一个获取临时STS身份凭证的接口。当这个接口(/get_sts_token)通过HTTP GET方法被访问时,它会生成一个临时身份凭证,并将其返回给请求者。

在ECS实例上,使用Flask框架快速搭建Web应用,实现一个获取临时STS身份凭证的接口的操作示例如下:

  1. 连接ECS实例。

    具体操作,请参见通过控制台使用ECS实例(快捷版)

  2. 安装Python3

  3. 创建项目文件夹,然后切换到项目目录。

    mkdir my_web_sample
    cd my_web_sample
  4. 安装依赖。

    pip3 install Flask
    pip3 install attr
    pip3 install yarl
    pip3 install async_timeout
    pip3 install idna_ssl
    pip3 install attrs
    pip3 install aiosignal
    pip3 install charset_normalizer
    pip3 install alibabacloud_tea_openapi
    pip3 install alibabacloud_sts20150401
    pip3 install alibabacloud_credentials
  5. 编写后端代码。

    1. 创建一个main.py文件。

    2. 在这个文件中,添加以下Python代码。

      import json
      from flask import Flask, render_template
      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
      
      app = Flask(__name__)
      
      # 将<YOUR_ROLE_ARN>替换为RAM角色的ARN。
      role_arn_for_oss_upload = '<YOUR_ROLE_ARN>'
      # 设置为STS服务的地域,例如cn-hangzhou。
      region_id = 'cn-hangzhou'
      
      @app.route("/")
      def hello_world():
          return render_template('index.html')
      
      @app.route('/get_sts_token', methods=['GET'])
      def get_sts_token():
          # 初始化 CredentialClient 时不指定参数,代表使用默认凭据链。
          # 在本地运行程序时,可以通过环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET 指定 AK;
          # 在 ECS\ECI\容器服务上运行时,可以通过环境变量 ALIBABA_CLOUD_ECS_METADATA 来指定绑定的实例节点角色,SDK 会自动换取 STS 临时凭证。
          config = Config(region_id=region_id, credential=CredentialClient())
          sts_client = Sts20150401Client(config=config)
          assume_role_request = sts_20150401_models.AssumeRoleRequest(
              role_arn=role_arn_for_oss_upload,
              # 将<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称。
              role_session_name='<YOUR_ROLE_SESSION_NAME>'
          )
          response = sts_client.assume_role(assume_role_request)
          token = json.dumps(response.body.credentials.to_map())
          return token
      
      app.run(host="0.0.0.0", port=80)
    3. 将代码中的<YOUR_ROLE_ARN>替换为步骤三获取的角色ARN。

    4. 将代码中的<YOUR_ROLE_SESSION_NAME>设置为自定义的会话名称,例如role_session_test

  6. 使用步骤一获取的访问密钥启动应用程序。

    ALIBABA_CLOUD_ACCESS_KEY_ID=<YOUR_AK_ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<YOUR_AK_SECRET> python3 main.py
  7. 在浏览器中访问http://<ECS实例公网IP地址>/get_sts_token

    成功返回示例如下:

    sts token.png

  8. Ctrl + C停止应用程序。

步骤七:在浏览器使用临时身份凭证上传文件到OSS

在业务服务器配置了获取STS临时身份凭证的接口后,在Web应用的前端网页使用CDN引入OSS JavaScript SDK,实现对文件上传的监听。当用户上传文件,调用/get_sts_token接口从业务服务器请求临时访问凭证,然后使用临时访问凭证向OSS上传文件。

在ECS上,使用CDN引入方式将OSS JavaScript SDK集成到Web应用的前端代码的操作示例如下

  1. Ctrl + C停止应用程序。

  2. 创建前端项目文件。

    mkdir templates
  3. 创建HTML模板文件。

    1. templates目录中创建一个index.html文件。

      vim templates/index.html
    2. 在这个文件中,添加以下HTML代码。

      <!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>
          <script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.17.0.min.js"></script>
        </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">
            let credentials = null;
            const form = document.querySelector("form");
            form.addEventListener("submit", async (event) => {
              event.preventDefault();
              // 临时凭证过期时,才重新获取,减少对 sts 服务的调用
              if (isCredentialsExpired(credentials)) {
                const response = await fetch("/get_sts_token", {
                  method: "GET",
                });
                if (!response.ok) {
                  // 处理错误的HTTP状态码
                  throw new Error(
                    `获取STS令牌失败: ${response.status} ${response.statusText}`
                  );
                }
                credentials = await response.json();
              }
              const client = new OSS({
                // 将<YOUR_BUCKET>设置为OSS Bucket名称。
                bucket: "<YOUR_BUCKET>",
                // 将<YOUR_REGION>设置为OSS Bucket所在地域,例如oss-cn-hangzhou。
                region: "<YOUR_REGION>",
                accessKeyId: credentials.AccessKeyId,
                accessKeySecret: credentials.AccessKeySecret,
                stsToken: credentials.SecurityToken,
              });
      
              const fileInput = document.querySelector("#file");
              const file = fileInput.files[0];
              const result = await client.put('uploads/'+file.name, file);
              console.log(result);
            });
      
            /**
             * 判断临时凭证是否到期。
             **/
            function isCredentialsExpired(credentials) {
              if (!credentials) {
                return true;
              }
              const expireDate = new Date(credentials.Expiration);
              const now = new Date();
              // 如果有效期不足一分钟,视为过期。
              return expireDate.getTime() - now.getTime() <= 60000;
            }
          </script>
        </body>
      </html>
    3. 将代码中的<YOUR_BUCKET<YOUR_REGION>替换为准备工作中创建的Bucket名称和Bucket所属地域ID。关于地域ID,请参见访问域名和数据中心

  4. 使用步骤一获取的访问密钥启动应用程序。

    ALIBABA_CLOUD_ACCESS_KEY_ID=<YOUR_AK_ID> ALIBABA_CLOUD_ACCESS_KEY_SECRET=<YOUR_AK_SECRET> python3 main.py
  5. 在浏览器中访问http://<ECS实例公网IP地址>,然后在页面中选择并上传文件,模拟真实用户在浏览器的行为。

    2024-05-10_11-10-45.png

完成与清理

方案验证

完成以上操作后,您可以查看文件是否已上传到OSS。

  1. 登录对象存储控制台

  2. 在左侧导航栏,选择Bucket列表

  3. Bucket列表页面,单击目标Bucket。

  4. 文件列表页面,查看成功上传的文件。

    1.png

清理资源

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

后续操作

终端用户在浏览器上传文件到OSS后:

  • 您可以为文件生成签名URL,授权用户在指定时间内下载或者预览文件。具体操作,请参见使用文件URL分享文件

  • 您可以对图片进行处理,例如添加图片水印、转换格式等。具体操作,请参见图片处理

常见问题

上传后如何获取文件URL?

您可以根据Bucket访问域名及文件访问路径获取文件的URL,详情请参见使用文件URL分享文件

  • 本页导读 (1)