为何不建议直接用 Javascript SDK 等客户端 SDK 来上传文件?

  1. 容易被反编译后暴露 ak / sk
  2. 不够灵活
安全建议:永远不要使用 API 下发 ak / sk

更好的方案

后端生成「预签名链接」并通过 API 下发

前端可以对预签名链接直接发起 PUT 请求,这样我们可以收获的好处有:

  1. 更安全:永远不会因 暴露客户端代码被反编译 而泄露至关重要的 AccessKey 和 SecretKey;
  2. 更灵活:利用显式指定 Content-TypeContent-Length 来防止客户端被破解,从而绕过业务策略的限制
  3. 低成本:统一由服务端传递预签名链接,客户端开发人员只需要完成 PUT HTTP 请求即可,不需要学习和调试不同语言下的 S3 SDK(事实证明,AWS SDK不同的语言版本由不同的团队维护,其规范、特性差异较大,不利于项目稳定。)
  4. 高性能:上传请求依然是直接对 缤纷云 S4 发起,不需要服务端中转。

如何运作

1

通过后端 API 生成用于上传的预签名链接:putObject-presigned-url

让后端程序(golang、python、php、nodejs)调用相应的 SDK 生成一个具有 PutObject 方法效力的「预签名」链接传递给 Web 页面的 Javascript 脚本。

2

客户端对 putObject-presigned-url 发起 PUT 请求,并完成文件上传

Javascript 脚本利用 网络请求组件(如 axios)来将本地文件 PUT 到上面提到的「预签名 PutObject 链接」

不同语言后端接口生成 PutObject 预签名链接示例:

import boto3
from flask import Flask
from flask import render_template_string
from flask import request

app = Flask(__name__)

@app.get('/s3_upload_url')
def get_upload_url():
    # Config
    s3endpoint = 'https://s3.bitiful.net' # 请填入控制台 “Bucket 设置” 页面底部的 “Endpoint” 标签中的信息
    s3region = 'cn-east-1'
    s3accessKeyId = '<--子账户 accessKey-->' # 请到控制台创建子账户,并为子账户创建相应 accessKey
    s3SecretKeyId = '<--子账户 secretKey-->' # !!切记,创建子账户时,需要手动为其分配具体权限!!

    # 连接 S3
    client = boto3.client(
        's3',
        aws_access_key_id = s3accessKeyId,
        aws_secret_access_key = s3SecretKeyId,
        endpoint_url = s3endpoint,
        region_name = s3region
    )
    url = client.generate_presigned_url(
        'put_object',
        Params={
            'Bucket': '<--缤纷云存储桶名-->',
            'Key': request.args.get('key'),
        },
        ExpiresIn=3600
    )
    return {'url': url}

前端上传示例:

upload_demo.web.html
<!DOCTYPE html>
<html>
<head>
  <title>S3 File Upload</title>
</head>
<body>
<div>
  <h1>S3 File Upload</h1>
  <input type="file" id="file">
  <button onclick="handleClick()">Upload</button>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.min.js"></script>
<script>
    let file;
    let fileContent;

    const fileInput = document.getElementById('file');
    const fileReader = new FileReader();

    fileInput.addEventListener('change', function (event) {
        file = event.target.files[0];
        console.log("file is: ", file);
        fileReader.readAsArrayBuffer(file);
    });

    fileReader.onload = function (event) {
        fileContent = event.target.result;
        // console.log("fileContent is: ", fileContent);
    };

    function handleClick() {
        console.log('click');

        const url = `/s3_upload_url`;
        const params = {key: '1.jpg'};

        axios.get(url, {params: params})
            .then(function (res) {
                console.log(res);
                const url = res.data.url;
                const config = {
                    headers: {
                        'Content-Type': null,
                    },
                };
                axios.put(url, file, config)
                    .then(function (res) {
                        console.log("res is: ", res);
                    });
            });

    }
</script>

</body>
</html>
上面的 Javascript 脚本在发起 Put 请求时将「Content-Type」设置为空值,建议使用缤纷云独有的 MIME 自动检测 功能。