日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

在客戶端直接上傳文件到OSS

更新時(shí)間: 2024-11-11 18:29:16

客戶端直傳是指客戶端直接上傳文件到對(duì)象存儲(chǔ)OSS。相對(duì)于服務(wù)端代理上傳,客戶端直傳避免了業(yè)務(wù)服務(wù)器中轉(zhuǎn)文件,提高了上傳速度,節(jié)省了服務(wù)器資源。本文介紹客戶端直傳的方案優(yōu)勢(shì)、安全實(shí)現(xiàn)和實(shí)踐參考。

為什么客戶端直傳

在典型的服務(wù)端和客戶端架構(gòu)下,常見的文件上傳方式是服務(wù)端代理上傳:客戶端將文件上傳到業(yè)務(wù)服務(wù)器,然后業(yè)務(wù)服務(wù)器將文件上傳到OSS。在這個(gè)過程中,一份數(shù)據(jù)需要在網(wǎng)絡(luò)上傳輸兩次,會(huì)造成網(wǎng)絡(luò)資源的浪費(fèi),增加服務(wù)端的資源開銷。為了解決這一問題,您可以在客戶端直連OSS來完成文件上傳,無需經(jīng)過業(yè)務(wù)服務(wù)器中轉(zhuǎn)。

image

如何實(shí)現(xiàn)客戶端直傳

實(shí)現(xiàn)客戶端直傳需要解決以下兩個(gè)大問題:

跨域訪問

如果您的客戶端是Web端或小程序,您需要解決跨域訪問被限制的問題。瀏覽器以及小程序容器出于安全考慮,通常都會(huì)限制跨域訪問,這一限制也會(huì)限制您的客戶端代碼直連OSS。您可以通過配置OSS Bucket的跨域訪問規(guī)則,來允許指定域名下的Web應(yīng)用或小程序直接訪問OSS。更多信息,請(qǐng)參見跨域設(shè)置

安全授權(quán)

上傳文件到OSS需要使用RAM用戶的訪問密鑰(AccessKey)來完成簽名認(rèn)證,但是在客戶端中使用長期有效的訪問密鑰,可能會(huì)導(dǎo)致訪問密鑰泄露,進(jìn)而引起安全問題。為了解決這一問題,您可以選擇以下方案實(shí)現(xiàn)安全上傳:

  • 服務(wù)端生成STS臨時(shí)訪問憑證

    對(duì)于大部分上傳文件的場景,建議您在服務(wù)端使用STS SDK獲取STS臨時(shí)訪問憑證,然后在客戶端使用STS臨時(shí)憑證和OSS SDK直接上傳文件。客戶端能重復(fù)使用服務(wù)端生成的STS臨時(shí)訪問憑證生成簽名,因此適用于基于分片上傳大文件、基于分片斷點(diǎn)續(xù)傳的場景。需要注意的是,頻繁地調(diào)用STS服務(wù)會(huì)引起限流,因此建議您對(duì)STS臨時(shí)憑證做緩存處理,并在有效期前刷新。為了確保STS臨時(shí)訪問憑證不被客戶端濫用,建議您為STS臨時(shí)訪問憑證添加額外的權(quán)限策略,以進(jìn)一步限制其權(quán)限。更多信息,請(qǐng)參見什么是STS

  • 服務(wù)端生成PostObject所需的簽名和Post Policy

    對(duì)于需要限制上傳文件屬性的場景,您可以在服務(wù)端生成PostObject所需的Post簽名、PostPolicy等信息,然后客戶端可以憑借這些信息,在一定的限制下不依賴OSS SDK直接上傳文件。您可以借助服務(wù)端生成的PostPolicy限制客戶端上傳的文件,例如限制文件大小、文件類型。此方案適用于通過HTML表單上傳的方式上傳文件。需要注意的是,此方案不支持基于分片上傳大文件、基于分片斷點(diǎn)續(xù)傳的場景。更多信息,請(qǐng)參見PostObject

  • 服務(wù)端生成PutObject所需的簽名URL

    對(duì)于簡單上傳文件的場景,您可以在服務(wù)端使用OSS SDK生成PutObject所需的簽名URL,客戶端可以憑借簽名URL,不依賴OSS SDK直接上傳文件。需要注意的是,此方案不適用于基于分片上傳大文件、基于分片斷點(diǎn)續(xù)傳的場景。在服務(wù)端對(duì)每個(gè)分片生成簽名URL,并將簽名URL返回給客戶端,會(huì)增加與服務(wù)端的交互次數(shù)和網(wǎng)絡(luò)請(qǐng)求的復(fù)雜性。另外,客戶端可能會(huì)修改分片的內(nèi)容或順序,導(dǎo)致最終合并的文件不正確。更多信息,請(qǐng)參見簽名版本1

服務(wù)端生成STS臨時(shí)訪問憑證

服務(wù)端通過STS臨時(shí)訪問憑證授權(quán)客戶端上傳文件到OSS的過程如下。

image
  1. 客戶端向業(yè)務(wù)服務(wù)器請(qǐng)求臨時(shí)訪問憑證。

  2. 業(yè)務(wù)服務(wù)器使用STS SDK調(diào)用AssumeRole接口,獲取臨時(shí)訪問憑證。

  3. STS生成并返回臨時(shí)訪問憑證給業(yè)務(wù)服務(wù)器。

  4. 業(yè)務(wù)服務(wù)器返回臨時(shí)訪問憑證給客戶端。

  5. 客戶端使用OSS SDK通過該臨時(shí)訪問憑證上傳文件到OSS。

  6. OSS返回成功響應(yīng)給客戶端。

示例代碼

以下示例代碼為代碼核心片段,如需查看完整代碼請(qǐng)參考示例工程:sts.zip

服務(wù)端示例代碼

服務(wù)端生成臨時(shí)訪問憑證的示例代碼如下:

說明

當(dāng)前代碼支持一鍵部署,您可以直接在函數(shù)計(jì)算FC中一鍵部署本代碼。oss-upload-sts-app

Python

import json
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

# 將<YOUR_ROLE_ARN>替換為擁有上傳文件到指定OSS Bucket權(quán)限的RAM角色的ARN。
role_arn_for_oss_upload = '<YOUR_ROLE_ARN>'
# 將<YOUR_REGION_ID>設(shè)置為STS服務(wù)的地域,例如cn-hangzhou。
region_id = '<YOUR_REGION_ID>'

def get_sts_token():
    # 初始化 CredentialClient 時(shí)不指定參數(shù),代表使用默認(rèn)憑據(jù)鏈。
    # 在本地運(yùn)行程序時(shí),可以通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET 指定 AK;
    # 在 ECS\ECI\容器服務(wù)上運(yùn)行時(shí),可以通過環(huán)境變量 ALIBABA_CLOUD_ECS_METADATA 來指定綁定的實(shí)例節(jié)點(diǎn)角色,SDK 會(huì)自動(dòng)換取 STS 臨時(shí)憑證。
    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>設(shè)置為自定義的會(huì)話名稱,例如oss-role-session。
        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

Java

import com.aliyun.sts20150401.Client;
import com.aliyun.sts20150401.models.AssumeRoleRequest;
import com.aliyun.sts20150401.models.AssumeRoleResponse;
import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
import com.aliyun.tea.TeaException;
import com.aliyun.teautil.models.RuntimeOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.aliyun.teaopenapi.models.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static com.aliyun.teautil.Common.assertAsString;

@RestController
public class StsController {

    @Autowired
    private Client stsClient;

    @GetMapping("/get_sts_token_for_oss_upload")
    public AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials generateStsToken() {
        AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest()
            .setDurationSeconds(3600L)
            // 將<YOUR_ROLE_SESSION_NAME>設(shè)置為自定義的會(huì)話名稱,例如 my-website-server。
            .setRoleSessionName("<YOUR_ROLE_SESSION_NAME>")
            // 將<YOUR_ROLE_ARN>替換為擁有上傳文件到指定OSS Bucket權(quán)限的RAM角色的ARN,可以在 RAM 角色詳情中獲得角色 ARN。
        RuntimeOptions runtime = new RuntimeOptions();
        try {
            AssumeRoleResponse response = stsClient.assumeRoleWithOptions(assumeRoleRequest, runtime);
            return response.body.credentials;
        } catch (TeaException error) {
            // 如有需要,請(qǐng)打印 error
            assertAsString(error.message);
            return null;
        } catch (Exception error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 如有需要,請(qǐng)打印 error
            assertAsString(error.message);
            return null;
        }
    }
}

@Configuration
public class StsClientConfiguration {

    @Bean
    public Client stsClient() {
        // 當(dāng)您在初始化憑據(jù)客戶端不傳入任何參數(shù)時(shí),Credentials工具會(huì)使用默認(rèn)憑據(jù)鏈方式初始化客戶端。
        Config config = new Config();
        config.endpoint = "sts.cn-hangzhou.aliyuncs.com";
        try {
            com.aliyun.credentials.Client credentials = new com.aliyun.credentials.Client();
            config.setCredential(credentials);
            return new Client(config);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

Go

package main

import (
    "encoding/json"
    "net/http"
    "os"

    openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
    sts20150401 "github.com/alibabacloud-go/sts-20150401/v2/client"
    util "github.com/alibabacloud-go/tea-utils/v2/service"
    "github.com/alibabacloud-go/tea/tea"
)

/**
 * 使用AK&SK初始化賬號(hào)Client
 * @param accessKeyId
 * @param accessKeySecret
 * @return Client
 * @throws Exception
 */
func CreateClient(accessKeyId *string, accessKeySecret *string) (*sts20150401.Client, error) {
    config := &openapi.Config{
    // 必填,您的 AccessKey ID
    AccessKeyId: accessKeyId,
    // 必填,您的 AccessKey Secret
    AccessKeySecret: accessKeySecret,
    }
    // Endpoint 請(qǐng)參考 https://api.aliyun.com/product/Sts
    config.Endpoint = tea.String("sts.cn-hangzhou.aliyuncs.com")
    return sts20150401.NewClient(config)
}

func AssumeRole(client *sts20150401.Client) (*sts20150401.AssumeRoleResponse, error) {
    assumeRoleRequest := &sts20150401.AssumeRoleRequest{
    DurationSeconds: tea.Int64(3600),
    RoleArn:         tea.String("acs:ram::1379186349531844:role/admin-oss"),
    RoleSessionName: tea.String("peiyu-demo"),
    }
    return client.AssumeRoleWithOptions(assumeRoleRequest, &util.RuntimeOptions{})
}

func handler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/" {
    http.ServeFile(w, r, "templates/index.html")
    return
    } else if r.URL.Path == "/get_sts_token_for_oss_upload" {
    client, err := CreateClient(tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")), tea.String(os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")))
    if err != nil {
    panic(err)
    }
    assumeRoleResponse, err := AssumeRole(client)
    if err != nil {
    panic(err)
    }
    responseBytes, err := json.Marshal(assumeRoleResponse)
    if err != nil {
    panic(err)
    }
    w.Header().Set("Content-Type", "application/json")
    w.Write(responseBytes)
    return
    }
    http.NotFound(w, r)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

PHP

<?php
require_once 'vendor/autoload.php';
use AlibabaCloud\Client\AlibabaCloud;
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
use AlibabaCloud\Sts\Sts;
// 初始化Alibaba Cloud客戶端。
AlibabaCloud::accessKeyClient(getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'), getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'))
    ->regionId('cn-hangzhou')
    ->asDefaultClient();
// 創(chuàng)建STS請(qǐng)求。
$request = Sts::v20150401()->assumeRole();
// 發(fā)起STS請(qǐng)求并獲取結(jié)果。
// 將<YOUR_ROLE_SESSION_NAME>設(shè)置為自定義的會(huì)話名稱,例如oss-role-session。
// 將<YOUR_ROLE_ARN>替換為擁有上傳文件到指定OSS Bucket權(quán)限的RAM角色的ARN。
$result = $request
    ->withRoleSessionName("<YOUR_ROLE_SESSION_NAME>")
    ->withDurationSeconds(3600)
    ->withRoleArn("<YOUR_ROLE_ARN>")
    ->request();
// 獲取STS請(qǐng)求結(jié)果中的憑證信息。
$credentials = $result->get('Credentials');
// 構(gòu)建返回的JSON數(shù)據(jù)。
$response = [
    'AccessKeyId' => $credentials['AccessKeyId'],
    'AccessKeySecret' => $credentials['AccessKeySecret'],
    'SecurityToken' => $credentials['SecurityToken'],
];
// 設(shè)置響應(yīng)頭為application/json。
header('Content-Type: application/json');
// 將結(jié)果轉(zhuǎn)換為JSON格式并打印。
echo json_encode(['Credentials' => $response]);
?>

Node.js

const express = require("express");
const { STS } = require('ali-oss');

const app = express();
const path = require("path");

app.use(express.static(path.join(__dirname, "templates")));
// 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
// 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_SECRET。
const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_SECRET;

app.get('/get_sts_token_for_oss_upload', (req, res) => {
  let sts = new STS({
   accessKeyId: accessKeyId,
   accessKeySecret: accessKeySecret
 });
   // roleArn填寫步驟2獲取的角色ARN,例如acs:ram::175708322470****:role/ramtest。
   // policy填寫自定義權(quán)限策略,用于進(jìn)一步限制STS臨時(shí)訪問憑證的權(quán)限。如果不指定Policy,則返回的STS臨時(shí)訪問憑證默認(rèn)擁有指定角色的所有權(quán)限。
   // 3000為過期時(shí)間,單位為秒。
   // sessionName用于自定義角色會(huì)話名稱,用來區(qū)分不同的令牌,例如填寫為sessiontest。
   sts.assumeRole('<YOUR_ROLE_ARN>', ``, '3000', 'sessiontest').then((result) => {
     console.log(result);
     res.json({
       AccessKeyId: result.credentials.AccessKeyId,
       AccessKeySecret: result.credentials.AccessKeySecret,
       SecurityToken: result.credentials.SecurityToken,
     });
   }).catch((err) => {
     console.log(err);
     res.status(400).json(err.message);
   });
 });

app.listen(8000, () => {
  console.log("http://127.0.0.1:8000");
});

Ruby

require 'sinatra'
require 'base64'
require 'open-uri'
require 'cgi'
require 'openssl'
require 'json'
require 'sinatra/reloader'
require 'sinatra/content_for'
require 'aliyunsdkcore'

# 設(shè)置public文件夾路徑為當(dāng)前文件夾下的templates文件夾
set :public_folder, File.dirname(__FILE__) + '/templates'

def get_sts_token_for_oss_upload()
  client = RPCClient.new(
    # 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
    access_key_id: ENV['ALIBABA_CLOUD_ACCESS_KEY_ID'],
    # 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。
    access_key_secret: ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET'],
    endpoint: 'https://sts.cn-hangzhou.aliyuncs.com',
    api_version: '2015-04-01'
  )
  response = client.request(
    action: 'AssumeRole',
    params: {
      # roleArn填寫步驟2獲取的角色ARN,例如acs:ram::175708322470****:role/ramtest。
      "RoleArn": "acs:ram::175708322470****:role/ramtest",
      # 3600為過期時(shí)間,單位為秒。
      "DurationSeconds": 3600,
      # sessionName用于自定義角色會(huì)話名稱,用來區(qū)分不同的令牌,例如填寫為sessiontest。
      "RoleSessionName": "sessiontest"
    },
    opts: {
      method: 'POST',
      format_params: true
    }
  )
end

if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

$server_ip = "0.0.0.0"
$server_port = 8000

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

set :bind, $server_ip
set :port, $server_port

get '/get_sts_token_for_oss_upload' do
  token = get_sts_token_for_oss_upload()
  response = {
    "AccessKeyId" => token["Credentials"]["AccessKeyId"],
    "AccessKeySecret" => token["Credentials"]["AccessKeySecret"],
    "SecurityToken" => token["Credentials"]["SecurityToken"]
  }
  response.to_json
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

C#

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Aliyun.OSS;
using System;
using System.IO;
using AlibabaCloud.SDK.Sts20150401;
using System.Text.Json;
namespace YourNamespace
{
    public class Program
    {
        private ILogger<Program> _logger;
        public static AlibabaCloud.SDK.Sts20150401.Client CreateClient(string accessKeyId, string accessKeySecret)
        {
            var config = new AlibabaCloud.OpenApiClient.Models.Config
            {
                AccessKeyId = accessKeyId,
                AccessKeySecret = accessKeySecret,
                Endpoint = "sts.cn-hangzhou.aliyuncs.com"
            };
            return new AlibabaCloud.SDK.Sts20150401.Client(config);
        }
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var app = builder.Build();
            builder.Logging.AddConsole();
            var serviceProvider = builder.Services.BuildServiceProvider();
            var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
            app.UseStaticFiles();
            app.MapGet("/", async (context) =>
            {
                var filePath = Path.Combine(Directory.GetCurrentDirectory(), "templates/index.html");
                var htmlContent = await File.ReadAllTextAsync(filePath);
                await context.Response.WriteAsync(htmlContent);
                logger.LogInformation("GET request to root path");
            });
            app.MapGet("/get_sts_token_for_oss_upload", async (context) =>
            {
                var program = new Program(logger);
                var client = CreateClient(Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID"), Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET"));
                var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
                // 將<YOUR_ROLE_SESSION_NAME>設(shè)置為自定義的會(huì)話名稱,例如oss-role-session。
                assumeRoleRequest.RoleSessionName = "<YOUR_ROLE_SESSION_NAME>";
                // 將<YOUR_ROLE_ARN>替換為擁有上傳文件到指定OSS Bucket權(quán)限的RAM角色的ARN。
                assumeRoleRequest.RoleArn = "<YOUR_ROLE_ARN>";
                assumeRoleRequest.DurationSeconds = 3600;
                var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
                var response = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
                var credentials = response.Body.Credentials;
                var jsonResponse = JsonSerializer.Serialize(new
                {
                    AccessKeyId = credentials.AccessKeyId,
                    AccessKeySecret = credentials.AccessKeySecret,
                    Expiration = credentials.Expiration,
                    SecurityToken = credentials.SecurityToken
                });
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(jsonResponse);
            });
            app.Run();
        }
        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }
    }
}

客戶端示例代碼

Web端使用臨時(shí)訪問憑證上傳文件到OSS的示例代碼如下:

let credentials = null;
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  // 臨時(shí)憑證過期時(shí),才重新獲取,減少對(duì)STS服務(wù)的調(diào)用。
  if (isCredentialsExpired(credentials)) {
    const response = await fetch("/get_sts_token_for_oss_upload", {
      method: "GET",
    });
    if (!response.ok) {
      // 處理錯(cuò)誤的HTTP狀態(tài)碼。
      throw new Error(
        `獲取STS令牌失敗: ${response.status} ${response.statusText}`
      );
    }
    credentials = await response.json();
  }
  const client = new OSS({
    // 將<YOUR_BUCKET>設(shè)置為OSS Bucket名稱。
    bucket: "<YOUR_BUCKET>",
    // 將<YOUR_REGION>設(shè)置為OSS Bucket所在地域,例如region: 'oss-cn-hangzhou'。
    region: "oss-<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(file.name, file);
  console.log(result);
});

/**
 * 判斷臨時(shí)憑證是否到期。
 **/
function isCredentialsExpired(credentials) {
  if (!credentials) {
    return true;
  }
  const expireDate = new Date(credentials.Expiration);
  const now = new Date();
  // 如果有效期不足一分鐘,視為過期。
  return expireDate.getTime() - now.getTime() <= 60000;
}

服務(wù)端生成PostObject所需的簽名和Post Policy

服務(wù)端通過Post簽名和Post Policy授權(quán)客戶端上傳文件到OSS的過程如下。

image
  1. 客戶端向業(yè)務(wù)服務(wù)器請(qǐng)求Post簽名和Post Policy等信息。

  2. 業(yè)務(wù)服務(wù)器生成并返回Post簽名和Post Policy等信息給客戶端。

  3. 客戶端使用Post簽名和Post Policy等信息調(diào)用PostObject通過HTML表單的方式上傳文件到OSS。

  4. OSS返回成功響應(yīng)給客戶端。

示例代碼

以下示例代碼為代碼核心片段,如需查看完整代碼請(qǐng)參考示例工程:postsignature.zip

服務(wù)端示例代碼

服務(wù)端生成Post簽名和Post Policy等信息的示例代碼如下:

說明

當(dāng)前代碼支持一鍵部署,您可以直接在函數(shù)計(jì)算FC中一鍵部署本代碼。oss-upload-post-signature-app

Python

import os
from hashlib import sha1 as sha
import json
import base64
import hmac
import datetime
import time

# 配置環(huán)境變量OSS_ACCESS_KEY_ID。
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
# 配置環(huán)境變量OSS_ACCESS_KEY_SECRET。
access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET')
# 將<YOUR_BUCKET>替換為Bucket名稱。
bucket = '<YOUR_BUCKET>'
# host的格式為bucketname.endpoint。將<YOUR_BUCKET>替換為Bucket名稱。將<YOUR_ENDPOINT>替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。
host = 'https://<YOUR_BUCKET>.<YOUR_ENDPOINT>'
# 指定上傳到OSS的文件前綴。
upload_dir = 'user-dir-prefix/'
# 指定過期時(shí)間,單位為秒。
expire_time = 3600


def generate_expiration(seconds):
    """
    通過指定有效的時(shí)長(秒)生成過期時(shí)間。
    :param seconds: 有效時(shí)長(秒)。
    :return: ISO8601 時(shí)間字符串,如:"2014-12-01T12:00:00.000Z"。
    """
    now = int(time.time())
    expiration_time = now + seconds
    gmt = datetime.datetime.utcfromtimestamp(expiration_time).isoformat()
    gmt += 'Z'
    return gmt


def generate_signature(access_key_secret, expiration, conditions, policy_extra_props=None):
    """
    生成簽名字符串Signature。
    :param access_key_secret: 有權(quán)限訪問目標(biāo)Bucket的AccessKeySecret。
    :param expiration: 簽名過期時(shí)間,按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ。示例值:"2014-12-01T12:00:00.000Z"。
    :param conditions: 策略條件,用于限制上傳表單時(shí)允許設(shè)置的值。
    :param policy_extra_props: 額外的policy參數(shù),后續(xù)如果policy新增參數(shù)支持,可以在通過dict傳入額外的參數(shù)。
    :return: signature,簽名字符串。
    """
    policy_dict = {
        'expiration': expiration,
        'conditions': conditions
    }
    if policy_extra_props is not None:
        policy_dict.update(policy_extra_props)
    policy = json.dumps(policy_dict).strip()
    policy_encode = base64.b64encode(policy.encode())
    h = hmac.new(access_key_secret.encode(), policy_encode, sha)
    sign_result = base64.b64encode(h.digest()).strip()
    return sign_result.decode()

def generate_upload_params():
    policy = {
        # 有效期。
        "expiration": generate_expiration(expire_time),
        # 約束條件。
        "conditions": [
            # 未指定success_action_redirect時(shí),上傳成功后的返回狀態(tài)碼,默認(rèn)為 204。
            ["eq", "$success_action_status", "200"],
            # 表單域的值必須以指定前綴開始。例如指定key的值以u(píng)ser/user1開始,則可以寫為["starts-with", "$key", "user/user1"]。
            ["starts-with", "$key", upload_dir],
            # 限制上傳Object的最小和最大允許大小,單位為字節(jié)。
            ["content-length-range", 1, 1000000],
            # 限制上傳的文件為指定的圖片類型
            ["in", "$content-type", ["image/jpg", "image/png"]]
        ]
    }
    signature = generate_signature(access_key_secret, policy.get('expiration'), policy.get('conditions'))
    response = {
        'policy': base64.b64encode(json.dumps(policy).encode('utf-8')).decode(),
        'ossAccessKeyId': access_key_id,
        'signature': signature,
        'host': host,
        'dir': upload_dir
        # 可以在這里再自行追加其他參數(shù)
    }
    return json.dumps(response)

Java

import com.aliyun.help.demo.uploading_to_oss_directly_postsignature.config.OssConfig;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.codehaus.jettison.json.JSONObject;
import java.util.Date;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import javax.annotation.PreDestroy;

@Controller
public class PostSignatureController {
    @Autowired
    private OSS ossClient;

    @Autowired
    private OssConfig ossConfig;

    @GetMapping("/get_post_signature_for_oss_upload")
    @ResponseBody
    public String generatePostSignature() {
        JSONObject response = new JSONObject();
        try {
            long expireEndTime = System.currentTimeMillis() + ossConfig.getExpireTime() * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, ossConfig.getDir());
            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);
            response.put("ossAccessKeyId", ossConfig.getAccessKeyId());
            response.put("policy", encodedPolicy);
            response.put("signature", postSignature);
            response.put("dir", ossConfig.getDir());
            response.put("host", ossConfig.getHost());
        } catch (
    OSSException oe) {
        System.out.println("Caught an OSSException, which means your request made it to OSS, "
                + "but was rejected with an error response for some reason.");
        // 假設(shè)此方法存在
        System.out.println("HTTP Status Code: " + oe.getRawResponseError()); 
        System.out.println("Error Message: " + oe.getErrorMessage());
        System.out.println("Error Code:       " + oe.getErrorCode());
        System.out.println("Request ID:      " + oe.getRequestId());
        System.out.println("Host ID:           " + oe.getHostId());
    } catch (ClientException ce) {
        System.out.println("Caught an ClientException, which means the client encountered "
                + "a serious internal problem while trying to communicate with OSS, "
                + "such as not being able to access the network.");
        System.out.println("Error Message: " + ce.getMessage());
    } finally {
        if (ossClient != null) {
            ossClient.shutdown();
        }
        return response.toString();
    }
  }
}

@Configuration
public class OssConfig {
    /**
     * 將 <YOUR-ENDPOINT> 替換為 Endpoint,例如 oss-cn-hangzhou.aliyuncs.com
     */
    private String endpoint = "<YOUR-ENDPOINT>";
    /**
     * 將 <YOUR-BUCKET> 替換為 Bucket 名稱
     */
    private String bucket = "<YOUR-BUCKET>";
    /**
     * 指定上傳到 OSS 的文件前綴
     */
    private String dir = "user-dir-prefix/";
    /**
     * 指定過期時(shí)間,單位為秒
     */
    private long expireTime = 3600;
    /**
     * 構(gòu)造 host
     */
    private String host = "http://" + bucket + "." + endpoint;
    /**
     * 通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID 設(shè)置 accessKeyId
     */
    private String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    /**
     * 通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET 設(shè)置 accessKeySecret
     */
    private String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");

    private OSS ossClient;
    @Bean
    public OSS getOssClient() {
        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        return ossClient;
    }
    @Bean
    public String getHost() {
        return host;
    }
    @Bean
    public String getAccessKeyId() {
        return accessKeyId;
    }
    @Bean
    public long getExpireTime() {
        return expireTime;
    }
    @Bean
    public String getDir() {
        return dir;
    }

    @PreDestroy
    public void onDestroy() {
        ossClient.shutdown();
    }
}

Go

package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

var (
	// 配置環(huán)境變量OSS_ACCESS_KEY_ID。
	accessKeyId = os.Getenv("OSS_ACCESS_KEY_ID")
	// 配置環(huán)境變量OSS_ACCESS_KEY_SECRET。
	accessKeySecret = os.Getenv("OSS_ACCESS_KEY_SECRET")
	// host的格式為bucketname.endpoint。將${your-bucket}替換為Bucket名稱。將${your-endpoint}替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。
	host = "http://${your-bucket}.${your-endpoint}"
	// 指定上傳到OSS的文件前綴。
	uploadDir = "user-dir-prefix/"
	// 指定過期時(shí)間,單位為秒。
	expireTime = int64(3600)
)

type ConfigStruct struct {
	Expiration string          `json:"expiration"`
	Conditions [][]interface{} `json:"conditions"`
}

type PolicyToken struct {
	AccessKeyId string `json:"ossAccessKeyId"`
	Host        string `json:"host"`
	Signature   string `json:"signature"`
	Policy      string `json:"policy"`
	Directory   string `json:"dir"`
}

func getGMTISO8601(expireEnd int64) string {
	return time.Unix(expireEnd, 0).UTC().Format("2006-01-02T15:04:05Z")
}

func getPolicyToken() string {
	now := time.Now().Unix()
	expireEnd := now + expireTime
	tokenExpire := getGMTISO8601(expireEnd)

	var config ConfigStruct
	config.Expiration = tokenExpire

	// 添加文件前綴限制
	config.Conditions = append(config.Conditions, []interface{}{"starts-with", "$key", uploadDir})

	// 添加文件大小限制,例如1KB到10MB
	minSize := int64(1024)
	maxSize := int64(10 * 1024 * 1024)
	config.Conditions = append(config.Conditions, []interface{}{"content-length-range", minSize, maxSize})

	result, err := json.Marshal(config)
	if err != nil {
		fmt.Println("callback json err:", err)
		return ""
	}

	encodedResult := base64.StdEncoding.EncodeToString(result)
	h := hmac.New(sha1.New, []byte(accessKeySecret))
	io.WriteString(h, encodedResult)
	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))

	policyToken := PolicyToken{
		AccessKeyId: accessKeyId,
		Host:        host,
		Signature:   signedStr,
		Policy:      encodedResult,
		Directory:   uploadDir,
	}

	response, err := json.Marshal(policyToken)
	if err != nil {
		fmt.Println("json err:", err)
		return ""
	}

	return string(response)
}

func handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		http.ServeFile(w, r, "templates/index.html")
		return
	} else if r.URL.Path == "/get_post_signature_for_oss_upload" {
		policyToken := getPolicyToken()
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(policyToken))
		return
	}
	http.NotFound(w, r)
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

PHP

<?php
function gmt_iso8601($time)
{
    return str_replace('+00:00', '.000Z', gmdate('c', $time));
}

// 從環(huán)境變量中獲取訪問憑證。運(yùn)行本示例代碼之前,請(qǐng)確保已設(shè)置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。
$accessKeyId = getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
$accessKeySecret = getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// $host的格式為<YOUR-BUCKET>.<YOUR-ENDPOINT>',請(qǐng)?zhí)鎿Q為您的真實(shí)信息。
$host = 'http://<YOUR-BUCKET>.<YOUR-ENDPOINT>';
// 用戶上傳文件時(shí)指定的前綴。
$dir = 'user-dir-prefix/';          

$now = time();
//設(shè)置該policy超時(shí)時(shí)間是10s. 即這個(gè)policy過了這個(gè)有效時(shí)間,將不能訪問。
$expire = 30;  
$end = $now + $expire;
$expiration = gmt_iso8601($end);

//最大文件大小.用戶可以自己設(shè)置。
$condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000);
$conditions[] = $condition;

// 表示用戶上傳的數(shù)據(jù),必須是以$dir開始,不然上傳會(huì)失敗,這一步不是必須項(xiàng),只是為了安全起見,防止用戶通過policy上傳到別人的目錄。
$start = array(0 => 'starts-with', 1 => '$key', 2 => $dir);
$conditions[] = $start;


$arr = array('expiration' => $expiration, 'conditions' => $conditions);
$policy = json_encode($arr);
$base64_policy = base64_encode($policy);
$string_to_sign = $base64_policy;
$signature = base64_encode(hash_hmac('sha1', $string_to_sign, $accessKeySecret, true));

$response = array();
$response['ossAccessKeyId'] = $accessKeyId;
$response['host'] = $host;
$response['policy'] = $base64_policy;
$response['signature'] = $signature;
$response['dir'] = $dir;  
echo json_encode($response);

Node.js

const express = require("express");
const { Buffer } = require("buffer");
const OSS = require("ali-oss");
const app = express();
const path = require("path");
const config = {
  // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
  accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
  // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。
  accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
  // 將<YOUR-BUCKET>替換為Bucket名稱。
  bucket: "<YOUR-BUCKET>",
  // 指定上傳到OSS的文件前綴。
  dir: "prefix/",
};

app.use(express.static(path.join(__dirname, "templates")));

app.get("/get_post_signature_for_oss_upload", async (req, res) => {
  const client = new OSS(config);
  const date = new Date();
  // 設(shè)置簽名的有效期,單位為秒。
  date.setSeconds(date.getSeconds() + 3600);
  const policy = {
    expiration: date.toISOString(),
    conditions: [
      // 設(shè)置上傳文件的大小限制。
      ["content-length-range", 0, 1048576000],
      // 限制可上傳的Bucket。
      { bucket: client.options.bucket },
    ],
  };
  const formData = await client.calculatePostSignature(policy);
  const host = `http://${config.bucket}.${
    (await client.getBucketLocation()).location
  }.aliyuncs.com`.toString();
  const params = {
    policy: formData.policy,
    signature: formData.Signature,
    ossAccessKeyId: formData.OSSAccessKeyId,
    host,
    dir: config.dir,
  };
  res.json(params);
});

app.get(/^(.+)*\.(html|js)$/i, async (req, res) => {
  res.sendFile(path.join(__dirname, "./templates", req.originalUrl));
});

app.listen(8000, () => {
  console.log("http://127.0.0.1:8000");
});

Ruby

require 'sinatra'
require 'base64'
require 'open-uri'
require 'cgi'
require 'openssl'
require 'json'
require 'sinatra/reloader'
require 'sinatra/content_for'

# 設(shè)置public文件夾路徑為當(dāng)前文件夾下的templates文件夾
set :public_folder, File.dirname(__FILE__) + '/templates'

# 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_ID']
# 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_SECRET']

# $host的格式為<bucketname>.<endpoint>,請(qǐng)?zhí)鎿Q為您的真實(shí)信息。
$host = 'http://<bucketname>.<endpoint>';

# 用戶上傳文件時(shí)指定的前綴。
$upload_dir = 'user-dir-prefix/'
# 過期時(shí)間,單位為秒。
$expire_time = 30
$server_ip = "0.0.0.0"
$server_port = 8000

if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

def hash_to_jason(source_hash)
  jason_string = source_hash.to_json;    

  jason_string.gsub!("\":[", "\": [")
  jason_string.gsub!("\",\"", "\", \"")
  jason_string.gsub!("],\"", "], \"")
  jason_string.gsub!("\":\"", "\": \"")

  jason_string
end


def get_token()
  expire_syncpoint = Time.now.to_i + $expire_time
  expire = Time.at(expire_syncpoint).utc.iso8601()
  response.headers['expire'] = expire
  policy_dict = {}
  condition_arrary = Array.new
  array_item = Array.new
  array_item.push('starts-with')
  array_item.push('$key')
  array_item.push($upload_dir)
  condition_arrary.push(array_item)
  policy_dict["conditions"] = condition_arrary
  policy_dict["expiration"] = expire
  policy = hash_to_jason(policy_dict)
  policy_encode = Base64.strict_encode64(policy).chomp;
  h = OpenSSL::HMAC.digest('sha1', $access_key_secret, policy_encode)
  hs = Digest::MD5.hexdigest(h)
  sign_result = Base64.strict_encode64(h).strip()
  token_dict = {}
  token_dict['ossAccessKeyId'] = $access_key_id
  token_dict['host'] = $host
  token_dict['policy'] = policy_encode
  token_dict['signature'] = sign_result 
  token_dict['expire'] = expire_syncpoint
  token_dict['dir'] = $upload_dir
  result = hash_to_jason(token_dict)
  result
end

set :bind, $server_ip
set :port, $server_port

get '/get_post_signature_for_oss_upload' do
  token = get_token()
  puts "Token: #{token}"
  token
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

end

if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

$server_ip = "0.0.0.0"
$server_port = 8000

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

set :bind, $server_ip
set :port, $server_port

get '/get_sts_token_for_oss_upload' do
  token = get_sts_token_for_oss_upload()
  response = {
    "AccessKeyId" => token["Credentials"]["AccessKeyId"],
    "AccessKeySecret" => token["Credentials"]["AccessKeySecret"],
    "SecurityToken" => token["Credentials"]["SecurityToken"]
  }
  response.to_json
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

C#

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Collections.Generic;
using System;
using System.Globalization;
using System.Text;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace YourNamespace
{
    public class Program
    {
        private ILogger<Program> _logger;
        // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // host的格式為bucketname.endpoint。將<YOUR-BUCKET>替換為Bucket名稱。將<YOUR-ENDPOINT>替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。
        public string Host { get; set; } = "<YOUR-BUCKET>.<YOUR-ENDPOINT>";
        // 指定上傳到OSS的文件前綴。
        public string UploadDir { get; set; } = "user-dir-prefix/";
        // 指定過期時(shí)間,單位為秒。
        public int ExpireTime { get; set; } = 3600;
        public class PolicyConfig
        {
            public string expiration { get; set; }
            public List<List<object>> conditions { get; set; }
        }
        public class PolicyToken
        {
            public string Accessid { get; set; }
            public string Policy { get; set; }
            public string Signature { get; set; }
            public string Dir { get; set; }
            public string Host { get; set; }
            public string Expire { get; set; }
        }
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var app = builder.Build();

            builder.Logging.AddConsole();
            var logger = builder.Services.BuildServiceProvider().GetRequiredService<ILogger<Program>>();

            app.UseStaticFiles(); 

            app.MapGet("/", async (context) =>
            {
                var filePath = Path.Combine(Directory.GetCurrentDirectory(), "templates/index.html");
                var htmlContent = await File.ReadAllTextAsync(filePath);
                await context.Response.WriteAsync(htmlContent);
                logger.LogInformation("GET request to root path");
            });

            app.MapGet("/get_post_signature_for_oss_upload", async (context) =>
            {
                var program = new Program(logger);
                var token = program.GetPolicyToken();

                logger.LogInformation($"Token: {token}");

                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(token);
            });

            app.Run();
        }

        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }

        private string ToUnixTime(DateTime dateTime)
        {
            return ((DateTimeOffset)dateTime).ToUnixTimeSeconds().ToString();
        }

        private string GetPolicyToken()
        {
            var expireDateTime = DateTime.Now.AddSeconds(ExpireTime);
            var config = new PolicyConfig
            {
                expiration = FormatIso8601Date(expireDateTime),
                conditions = new List<List<object>>()
            };
            config.conditions.Add(new List<object>
            {
                "content-length-range", 0, 1048576000
            });
            var policy = JsonConvert.SerializeObject(config);
            var policyBase64 = EncodeBase64("utf-8", policy);
            var signature = ComputeSignature(AccessKeySecret, policyBase64);
            var policyToken = new PolicyToken
            {
                Accessid = AccessKeyId,
                Host = Host,
                Policy = policyBase64,
                Signature = signature,
                Expire = ToUnixTime(expireDateTime),
                Dir = UploadDir
            };
            return JsonConvert.SerializeObject(policyToken);
        }

        private string FormatIso8601Date(DateTime dtime)
        {
            return dtime.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
                                    CultureInfo.CurrentCulture);
        }

        private string EncodeBase64(string codeType, string code)
        {
            string encode = "";
            byte[] bytes = Encoding.GetEncoding(codeType).GetBytes(code);
            try
            {
                encode = Convert.ToBase64String(bytes);
            }
            catch
            {
                encode = code;
            }
            return encode;
        }

        private string ComputeSignature(string key, string data)
        {
            using (var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(key)))
            {
                return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(data)));
            }
        }
    }
}

客戶端示例代碼

Web端使用Post簽名和Post Policy等信息上傳文件到OSS的示例代碼如下:

const form = document.querySelector("form");
const fileInput = document.querySelector("#file");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const file = fileInput.files[0];
  const filename = fileInput.files[0].name;
  fetch("/get_post_signature_for_oss_upload", { method: "GET" })
    .then((response) => {
      if (!response.ok) {
        throw new Error("獲取簽名失敗");
      }
      return response.json();
    })
    .then((data) => {
      const formData = new FormData();
      formData.append("name", filename);
      formData.append("policy", data.policy);
      formData.append("OSSAccessKeyId", data.ossAccessKeyId);
      formData.append("success_action_status", "200");
      formData.append("signature", data.signature);
      formData.append("key", data.dir + filename);
      formData.append("file", file);

      return fetch(data.host, { method: "POST", body: formData });
    })
    .then((response) => {
      if (response.ok) {
        console.log("上傳成功");
        alert("文件已上傳");
      } else {
        console.log("上傳失敗", response);
        alert("上傳失敗,請(qǐng)稍后再試");
      }
    })
    .catch((error) => {
      console.error("發(fā)生錯(cuò)誤:", error);
    });
});

服務(wù)端生成PutObject所需的簽名URL

服務(wù)端通過簽名URL授權(quán)客戶端上傳文件到OSS的過程如下。

image
  1. 客戶端向業(yè)務(wù)服務(wù)器請(qǐng)求簽名URL。

  2. 業(yè)務(wù)服務(wù)器使用OSS SDK生成PUT類型的簽名URL,然后將其返回給客戶端。

  3. 客戶端使用PUT類型的簽名URL調(diào)用PutObject上傳文件到OSS。

  4. OSS向客戶端返回成功響應(yīng)。

示例代碼

以下示例代碼為代碼核心片段,如需查看完整代碼請(qǐng)參考示例工程:presignedurl.zip

服務(wù)端示例代碼

服務(wù)端生成簽名URL的示例代碼如下:

說明

當(dāng)前代碼支持一鍵部署,您可以直接在函數(shù)計(jì)算FC中一鍵部署本代碼。oss-upload-presigned-url-app

Python

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# 從環(huán)境變量中獲取訪問憑證。運(yùn)行本示例代碼之前,請(qǐng)確保已設(shè)置環(huán)境變量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# 將<YOUR_ENDPOINT>替換為Bucket所在地域?qū)?yīng)的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
# 將<YOUR_BUCKET>替換為Bucket名稱。
bucket = oss2.Bucket(auth, '<YOUR_ENDPOINT>', '<YOUR_BUCKET>')
# 指定過期時(shí)間,單位秒。
expire_time = 3600
# 填寫Object完整路徑,例如exampledir/exampleobject.png。Object完整路徑中不能包含Bucket名稱。
object_name = 'exampledir/exampleobject.png'

def generate_presigned_url():
    # 指定Header。
    headers = dict()
    # 指定Content-Type。
    headers['Content-Type'] = 'image/png'
    # 指定存儲(chǔ)類型。
    # headers["x-oss-storage-class"] = "Standard"
    # 生成簽名URL時(shí),OSS默認(rèn)會(huì)對(duì)Object完整路徑中的正斜線(/)進(jìn)行轉(zhuǎn)義,從而導(dǎo)致生成的簽名URL無法直接使用。
    # 設(shè)置slash_safe為True,OSS不會(huì)對(duì)Object完整路徑中的正斜線(/)進(jìn)行轉(zhuǎn)義,此時(shí)生成的簽名URL可以直接使用。
    url = bucket.sign_url('PUT', object_name, expire_time, slash_safe=True, headers=headers)
    return url

Java

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.net.URL;
import java.util.Date;
import javax.annotation.PreDestroy;

@Configuration
public class OssConfig {

    /**
     * 將<your-endpoint>替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。
     */
    private static final String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";

    /**
     * 通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID 設(shè)置 accessKeyId
     */
    @Value("${ALIBABA_CLOUD_ACCESS_KEY_ID}")
    private String accessKeyId;

    /**
     * 通過環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_Secret 設(shè)置 accessKeySecret
     */
    @Value("${ALIBABA_CLOUD_ACCESS_KEY_SECRET}")
    private String accessKeySecret;

    private OSS ossClient;


    @Bean
    public OSS getSssClient() {
        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        return ossClient;
    }

    @PreDestroy
    public void onDestroy() {
        ossClient.shutdown();
    }
}

@Controller
public class PresignedURLController {

    /**
     * 將<your-bucket>替換為Bucket名稱。
     * 指定上傳到OSS的文件前綴。
     * 將<your-object>替換為Object完整路徑,例如exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
     * 指定過期時(shí)間,單位為毫秒。
     */
    private static final String BUCKET_NAME = "<your-bucket>";
    private static final String OBJECT_NAME = "<your-object>";
    private static final long EXPIRE_TIME = 3600 * 1000L;

    @Autowired
    private OSS ossClient;

    @GetMapping("/get_presigned_url_for_oss_upload")
    @ResponseBody
    public String generatePresignedURL() {

        try {
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(BUCKET_NAME, OBJECT_NAME, HttpMethod.PUT);
            Date expiration = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            request.setExpiration(expiration);
            request.setContentType("image/png");
            URL signedUrl = ossClient.generatePresignedUrl(request);
            return signedUrl.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Go

package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
)

func getURL() string {
	// yourEndpoint填寫B(tài)ucket對(duì)應(yīng)的Endpoint,以華東1(杭州)為例,填寫為https://oss-cn-hangzhou.aliyuncs.com。其它Region請(qǐng)按實(shí)際情況填寫。
	endpoint := "https://oss-cn-beijing.aliyuncs.com"
	// 填寫B(tài)ucket名稱,例如examplebucket。
	bucketName := "examplebucket"
	// 填寫文件完整路徑,例如exampledir/exampleobject.txt。文件完整路徑中不能包含Bucket名稱。
	objectName := "exampledir/exampleobject.txt"
	// 從環(huán)境變量中獲取訪問憑證。運(yùn)行本示例代碼之前,請(qǐng)確保已設(shè)置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。
	accessKeyID := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
	accessKeySecret := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")

	client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
	if err != nil {
		fmt.Println("json err:", err)
	}
	bucket, err := client.Bucket(bucketName)
	if err != nil {
		fmt.Println("json err:", err)
	}
	options := []oss.Option{
		oss.ContentType("image/png"),
	}
	signedURL, err := bucket.SignURL(objectName, oss.HTTPPut, 60, options...)
	if err != nil {
		fmt.Println("json err:", err)
	}

	return signedURL
}

func handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		http.ServeFile(w, r, "templates/index.html")
		return
	} else if r.URL.Path == "/get_presigned_url_for_oss_upload" {
		url := getURL()
		fmt.Fprintf(w, "%s", url)
		return
	}
	http.NotFound(w, r)
}
func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

PHP

<?php
require_once __DIR__ . '/vendor/autoload.php';
use OSS\OssClient;
use OSS\Core\OssException;
use OSS\Http\RequestCore;
use OSS\Http\ResponseCore;
// 運(yùn)行本示例代碼之前,請(qǐng)確保已設(shè)置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。
$accessKeyId = getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
$accessKeySecret = getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// yourEndpoint填寫B(tài)ucket所在地域?qū)?yīng)的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
$endpoint = "<YOUR-ENDPOINT>";
// 填寫B(tài)ucket名稱。
$bucket= "<YOUR-BUCKET>";
// 填寫不包含Bucket名稱在內(nèi)的Object完整路徑。
$object = "test.png";
// 設(shè)置簽名URL的有效時(shí)長為3600秒。
$timeout = 3600;
try {
    $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false);
    // 生成簽名URL。
    $signedUrl = $ossClient->signUrl($bucket, $object, $timeout, "PUT", array('Content-Type' => 'image/png'));
    // 打印返回的數(shù)據(jù)
    echo $signedUrl;
} catch (OssException $e) {
    printf($e->getMessage() . "\n");
    return;
}

Node.js

const express = require("express");
const { Buffer } = require("buffer");
const OSS = require("ali-oss");
const app = express();
const path = require("path");
const fs = require("fs");
const axios = require("axios");

const config = {
  // <YOURREGION>填寫B(tài)ucket所在地域。以華東1(杭州)為例,Region填寫為oss-cn-hangzhou。
  region: '<YOURREGION>',
  // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
  accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
  // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。
  accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
  // 將<YOUR-BUCKET>替換為Bucket名稱。
  bucket: "<YOUR-BUCKET>",
}
const object = "examplefile.png";

app.use(express.static(path.join(__dirname, "templates")));

app.get("/get_presigned_url_for_oss_upload", async (req, res) => {
    const client = new OSS(config);
    const url = client.signatureUrl(object, {
        method: "PUT",
        "Content-Type": "application/x-www-form-urlencoded",
    });
    res.send(url); 
    console.log(url);
  });

app.listen(8000, () => {
  console.log("http://127.0.0.1:8000");
});

Ruby

require 'sinatra'
require 'base64'
require 'open-uri'
require 'cgi'
require 'openssl'
require 'json'
require 'sinatra/reloader'
require 'sinatra/content_for'
require 'aliyun/oss'
include Aliyun::OSS

# 設(shè)置public文件夾路徑為當(dāng)前文件夾下的templates文件夾
set :public_folder, File.dirname(__FILE__) + '/templates'

# 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_KEY_ID']
# 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_SECRET。
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET']

# 填寫Object完整路徑,例如exampledir/exampleobject.png。Object完整路徑中不能包含Bucket名稱。
object_key = 'exampledir/exampleobject.png'

def get_presigned_url(client, object_key)
  # 將<YOUR-BUCKET>替換為Bucket名稱。
  bucket = client.get_bucket('<YOUR-BUCKET>')
  # 生成簽名URL,并指定URL有效時(shí)間為1小時(shí)(3600秒)。
  bucket.object_url(object_key, 3600)
end

client = Aliyun::OSS::Client.new(
  # 將<YOUR-ENDPOINT>替換為Bucket所在地域?qū)?yīng)的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
  endpoint: '<YOUR-ENDPOINT>',
  # 從環(huán)境變量中獲取訪問憑證。運(yùn)行本示例代碼之前,請(qǐng)確保已設(shè)置環(huán)境變量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
  access_key_id: $access_key_id,
  access_key_secret: $access_key_secret
)


if ARGV.length == 1 
  $server_port = ARGV[0]
elsif ARGV.length == 2
  $server_ip = ARGV[0]
  $server_port = ARGV[1]
end

$server_ip = "0.0.0.0"
$server_port = 8000

puts "App server is running on: http://#{$server_ip}:#{$server_port}"

set :bind, $server_ip
set :port, $server_port

get '/get_presigned_url_for_oss_upload' do
  url = get_presigned_url(client, object_key.to_s)
  puts "Token: #{url}"
  url
end

get '/*' do
  puts "********************* GET "
  send_file File.join(settings.public_folder, 'index.html')
end

C#

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
using System.IO;
using System;
using Microsoft.Extensions.Logging;
using Aliyun.OSS;

namespace YourNamespace
{
    public class Program
    {
        private ILogger<Program> _logger;

        // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_ID。
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // 配置環(huán)境變量ALIBABA_CLOUD_ACCESS_KEY_SECRET。
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // <YOUR-ENDPOINT>替換為Bucket所在地域?qū)?yīng)的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
        private string EndPoint { get; set; } = "<YOUR-ENDPOINT>";
        // 將<YOUR-BUCKET>替換為Bucket名稱。
        private string BucketName { get; set; } = "<YOUR-BUCKET>";
        private string ObjectName { get; set; } = "exampledir/exampleobject2.png";

        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var app = builder.Build();

            // 添加日志
            builder.Logging.AddConsole();
            var logger = builder.Services.BuildServiceProvider().GetRequiredService<ILogger<Program>>();

            app.UseStaticFiles(); // 添加這行以啟用靜態(tài)文件中間件

            app.MapGet("/", async (context) =>
            {
                var filePath = Path.Combine(Directory.GetCurrentDirectory(), "templates/index.html");
                var htmlContent = await File.ReadAllTextAsync(filePath);
                await context.Response.WriteAsync(htmlContent);

                // 打印日志
                logger.LogInformation("GET request to root path");
            });

            app.MapGet("/get_presigned_url_for_oss_upload", async (context) =>
            {
                var program = new Program(logger);
                var signedUrl = program.GetSignedUrl();

                logger.LogInformation($"SignedUrl: {signedUrl}"); // 打印token的值
                await context.Response.WriteAsync(signedUrl);
            });

            app.Run();
        }

        // 構(gòu)造函數(shù)注入ILogger
        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }

        private string GetSignedUrl()
        {
            // 創(chuàng)建OSSClient實(shí)例
            var ossClient = new OssClient(EndPoint, AccessKeyId, AccessKeySecret);

            // 生成簽名URL
            var generatePresignedUriRequest = new GeneratePresignedUriRequest(BucketName, ObjectName, SignHttpMethod.Put)
            {
                Expiration = DateTime.Now.AddHours(1),
                ContentType = "image/png"
            };
            var signedUrl = ossClient.GeneratePresignedUri(generatePresignedUriRequest);

            return signedUrl.ToString();
        }
    }
}

客戶端示例代碼

Web端使用簽名URL上傳文件到OSS的示例代碼如下:

const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const fileInput = document.querySelector("#file");
  const file = fileInput.files[0];
  fetch("/get_presigned_url_for_oss_upload", { method: "GET" })
    .then((response) => {
      if (!response.ok) {
        throw new Error("獲取預(yù)簽名URL失敗");
      }
      return response.text();
    })
    .then((url) => {
      fetch(url, {
        method: "PUT",
        headers: new Headers({
          "Content-Type": "image/png",
        }),
        body: file,
      }).then((response) => {
        if (!response.ok) {
          throw new Error("文件上傳到OSS失敗");
        }
        console.log(response);
        alert("文件已上傳");
      });
    })
    .catch((error) => {
      console.error("發(fā)生錯(cuò)誤:", error);
      alert(error.message);
    });
});

客戶端直傳實(shí)踐參考

不同類型的客戶端的直傳實(shí)踐參考如下:

上一篇: 實(shí)踐教程概述 下一篇: Web端直傳實(shí)踐
阿里云首頁 對(duì)象存儲(chǔ) 相關(guān)技術(shù)圈