All Products
Search
Document Center

Object Storage Service:Authorize access (Browser.js SDK)

Last Updated:Mar 20, 2026

Use Security Token Service (STS) or signed URLs to let browser clients upload files to OSS without embedding long-term credentials in frontend code.

Choose an authorization method

Three methods are available. Each keeps your AccessKey pair on the server while giving the browser client just enough permission to upload.

MethodBest forLimitations
STS temporary credentials (recommended)Most upload scenarios; supports multipart and resumable uploadsFrequent AssumeRole calls may trigger throttling — cache and refresh credentials
PostObject signature + PostPolicyHTML form uploads with file-type or size restrictions; client can upload directly without using an OSS SDKDoes not support multipart or resumable uploads
Signed URL for PutObjectSimple single-file uploads without an OSS SDK on the clientDoes not support multipart or resumable uploads

STS temporary credentials are the recommended choice for most scenarios. The client can reuse the same credentials across multiple requests and generate its own signatures, which works well for large-file multipart and resumable uploads.

Method 1: STS temporary credentials (recommended)

How it works

  1. The browser client requests temporary credentials from your application server.

  2. The application server calls the STS AssumeRole operation to get temporary credentials.

  3. STS returns the credentials to the application server.

  4. The application server returns the credentials to the browser client.

  5. The browser client initializes an OSS SDK instance with the credentials and uploads the file.

  6. OSS returns a success response.

STS upload flow
Important

When you use STS credentials to generate a signed URL, the shorter of the two expiration times applies. For example, if the STS credentials expire in 1,200 seconds and the signed URL is set to expire in 3,600 seconds, the signed URL stops working when the STS credentials expire — even though the URL itself has not reached its expiration time yet.

Sample code

The following snippets show the core logic. For the complete runnable project, download sts.zip.

Server side

The server exposes a GET /get_sts_token_for_oss_upload endpoint. All examples call the STS AssumeRole operation and return AccessKeyId, AccessKeySecret, and SecurityToken to the client.

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)
            // Set <YOUR_ROLE_SESSION_NAME> to a custom session name, for example, my-website-server.
            .setRoleSessionName("<YOUR_ROLE_SESSION_NAME>")
            // Replace <YOUR_ROLE_ARN> with the ARN of the RAM role that has permissions to upload files to the specified OSS bucket. You can obtain the role ARN from the RAM role details.
        RuntimeOptions runtime = new RuntimeOptions();
        try {
            AssumeRoleResponse response = stsClient.assumeRoleWithOptions(assumeRoleRequest, runtime);
            return response.body.credentials;
        } catch (TeaException error) {
            // If needed, log the error.
            assertAsString(error.message);
            return null;
        } catch (Exception error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // If needed, log the error.
            assertAsString(error.message);
            return null;
        }
    }
}

@Configuration
public class StsClientConfiguration {

    @Bean
    public Client stsClient() {
        // If you do not pass any parameters when you initialize the credential client, the credentials tool uses the default provider chain to initialize the client.
        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;
        }
    }

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")));
// Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
// Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
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
 });
   // Set roleArn to the role ARN that you obtained in Step 2, for example, acs:ram::175708322470****:role/ramtest.
   // Specify a custom policy to further limit the permissions of the STS temporary access credentials. If you do not specify a policy, the returned STS temporary access credentials have all the permissions of the specified role by default.
   // 3000 is the expiration time in seconds.
   // Use sessionName to specify a custom role session name to distinguish different tokens, for example, 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");
});

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

# Replace <YOUR_ROLE_ARN> with the ARN of the RAM role that has permissions to upload files to the specified OSS bucket.
role_arn_for_oss_upload = '<YOUR_ROLE_ARN>'
# Set <YOUR_REGION_ID> to the region of the STS service, for example, cn-hangzhou.
region_id = '<YOUR_REGION_ID>'

def get_sts_token():
    # If you do not specify parameters when you initialize CredentialClient, the default provider chain is used.
    # When you run the program locally, you can specify the AccessKey pair using the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.
    # When you run the program on an ECS instance, an ECI instance, or in a container, you can specify the attached instance RAM role using the ALIBABA_CLOUD_ECS_METADATA environment variable. The SDK automatically retrieves the STS temporary credentials.
    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,
        # Set <YOUR_ROLE_SESSION_NAME> to a custom session name, for example, 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

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"
)

/**
 * Initialize the account client using an AccessKey pair.
 * @param accessKeyId
 * @param accessKeySecret
 * @return Client
 * @throws Exception
 */
func CreateClient(accessKeyId *string, accessKeySecret *string) (*sts20150401.Client, error) {
    config := &openapi.Config{
    // Required. Your AccessKey ID.
    AccessKeyId: accessKeyId,
    // Required. Your AccessKey secret.
    AccessKeySecret: accessKeySecret,
    }
    // For more information about endpoints, see 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;
// Initialize the Alibaba Cloud client.
AlibabaCloud::accessKeyClient(getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'), getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'))
    ->regionId('cn-hangzhou')
    ->asDefaultClient();
// Create an STS request.
$request = Sts::v20150401()->assumeRole();
// Send the STS request and obtain the result.
// Set <YOUR_ROLE_SESSION_NAME> to a custom session name, for example, oss-role-session.
// Replace <YOUR_ROLE_ARN> with the ARN of the RAM role that has permissions to upload files to the specified OSS bucket.
$result = $request
    ->withRoleSessionName("<YOUR_ROLE_SESSION_NAME>")
    ->withDurationSeconds(3600)
    ->withRoleArn("<YOUR_ROLE_ARN>")
    ->request();
// Obtain the credential information from the STS request result.
$credentials = $result->get('Credentials');
// Construct the JSON data to return.
$response = [
    'AccessKeyId' => $credentials['AccessKeyId'],
    'AccessKeySecret' => $credentials['AccessKeySecret'],
    'SecurityToken' => $credentials['SecurityToken'],
];
// Set the response header to application/json.
header('Content-Type: application/json');
// Convert the result to the JSON format and print it.
echo json_encode(['Credentials' => $response]);
?>

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();
                // Set <YOUR_ROLE_SESSION_NAME> to a custom session name, for example, oss-role-session.
                assumeRoleRequest.RoleSessionName = "<YOUR_ROLE_SESSION_NAME>";
                // Replace <YOUR_ROLE_ARN> with the ARN of the RAM role that has permissions to upload files to the specified OSS bucket.
                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;
        }
    }
}

Ruby

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

# Set the path of the public folder to the templates folder in the current directory.
set :public_folder, File.dirname(__FILE__) + '/templates'

def get_sts_token_for_oss_upload()
  client = RPCClient.new(
    # Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
    access_key_id: ENV['ALIBABA_CLOUD_ACCESS_KEY_ID'],
    # Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
    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: {
      # Set RoleArn to the role ARN that you obtained in Step 2, for example, acs:ram::175708322470****:role/ramtest.
      "RoleArn": "acs:ram::175708322470****:role/ramtest",
      # 3600 is the expiration time in seconds.
      "DurationSeconds": 3600,
      # Use RoleSessionName to specify a custom role session name to distinguish different tokens, for example, sessiontest.
      "RoleSessionName": "sessiontest"
    },
    opts: {
      method: 'POST',
      format_params: true
    }
  )
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

Client side

The browser client fetches credentials from the server only when the current credentials are about to expire (within 60 seconds). It then initializes an OSS client instance and calls client.put() to upload the file.

JavaScript

let credentials = null;
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  // To reduce calls to the STS service, obtain the temporary credentials again only when the current credentials expire.
  if (isCredentialsExpired(credentials)) {
    const response = await fetch("/get_sts_token_for_oss_upload", {
      method: "GET",
    });
    if (!response.ok) {
      // Handle the error HTTP status code.
      throw new Error(
        `Failed to obtain the STS token: ${response.status} ${response.statusText}`
      );
    }
    credentials = await response.json();
  }
  const client = new OSS({
    // Set <YOUR_BUCKET> to the name of your OSS bucket.
    bucket: "<YOUR_BUCKET>",
    // Set <YOUR_REGION> to the region where the OSS bucket is located, for example, region: 'oss-cn-hangzhou'.
    region: "oss-<YOUR_REGION>",
    authorizationV4: true,
    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);
});

/**
 * Check whether the temporary credentials have expired.
 **/
function isCredentialsExpired(credentials) {
  if (!credentials) {
    return true;
  }
  const expireDate = new Date(credentials.Expiration);
  const now = new Date();
  // If the validity period is less than one minute, the credentials are considered expired.
  return expireDate.getTime() - now.getTime() <= 60000;
}

Credential caching and permission scoping

  • Cache credentials on the client. Calling AssumeRole on every upload request can trigger throttling. Reuse the same credentials until they are about to expire, then refresh.

  • Attach an access policy to limit scope. By default, the returned credentials carry all permissions of the RAM role. Attach an inline policy to restrict operations to specific buckets or prefixes.

Method 2: PostObject signature and PostPolicy

How it works

  1. The browser client requests upload parameters from the application server.

  2. The server generates a PostObject signature and PostPolicy, then returns them to the client.

  3. The client submits an HTML form with the signature and PostPolicy to call the PostObject operation directly.

  4. OSS returns a success response.

PostObject upload flow

This method is suitable when you need to restrict what clients can upload — for example, limiting file size or content type — without requiring the client to use an OSS SDK. The PostPolicy is enforced server-side, so clients can only upload files that match the policy conditions.

This method does not support multipart or resumable uploads.

Sample code

The following snippets show the core logic. For the complete runnable project, download postsignature.zip.

Server side

The server exposes a GET /get_post_signature_for_oss_upload endpoint. All examples generate the PostPolicy, compute the HMAC-SHA1 signature, and return ossAccessKeyId, policy, signature, host, and dir to the client.

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.");
        // Assume that this method exists.
        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 {
    /**
     * Replace <YOUR-ENDPOINT> with an endpoint, for example, oss-cn-hangzhou.aliyuncs.com.
     */
    private String endpoint = "<YOUR-ENDPOINT>";
    /**
     * Replace <YOUR-BUCKET> with a bucket name.
     */
    private String bucket = "<YOUR-BUCKET>";
    /**
     * Specify the prefix of the file to upload to OSS.
     */
    private String dir = "user-dir-prefix/";
    /**
     * Specify the expiration time in seconds.
     */
    private long expireTime = 3600;
    /**
     * Construct the host.
     */
    private String host = "http://" + bucket + "." + endpoint;
    /**
     * Set accessKeyId using the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
     */
    private String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    /**
     * Set accessKeySecret using the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
     */
    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();
    }
}

Node.js

const express = require("express");
const { Buffer } = require("buffer");
const OSS = require("ali-oss");
const app = express();
const path = require("path");
const config = {
  // Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
  accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
  // Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
  accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
  // Replace <YOUR-BUCKET> with the bucket name.
  bucket: "<YOUR-BUCKET>",
  // Specify the prefix of the file to upload to 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();
  // Set the validity period of the signature in seconds.
  date.setSeconds(date.getSeconds() + 3600);
  const policy = {
    expiration: date.toISOString(),
    conditions: [
      // Set the size limit for the uploaded file.
      ["content-length-range", 0, 1048576000],
      // Specify the bucket to which files can be uploaded.
      { 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");
});

Python

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

# Configure the OSS_ACCESS_KEY_ID environment variable.
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
# Configure the OSS_ACCESS_KEY_SECRET environment variable.
access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET')
# Replace <YOUR_BUCKET> with the bucket name.
bucket = '<YOUR_BUCKET>'
# The host is in the format of bucketname.endpoint. Replace <YOUR_BUCKET> with the bucket name. Replace <YOUR_ENDPOINT> with the OSS endpoint, for example, oss-cn-hangzhou.aliyuncs.com.
host = 'https://<YOUR_BUCKET>.<YOUR_ENDPOINT>'
# Specify the prefix of the file to upload to OSS.
upload_dir = 'user-dir-prefix/'
# Specify the expiration time in seconds.
expire_time = 3600


def generate_expiration(seconds):
    """
    Generate an expiration time by specifying a validity period in seconds.
    :param seconds: The validity period in seconds.
    :return: An ISO 8601 time string, such as "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):
    """
    Generate the signature string.
    :param access_key_secret: The AccessKey secret of an account that has permissions to access the destination bucket.
    :param expiration: The expiration time of the signature in the ISO 8601 standard. The time must be in UTC and in the yyyy-MM-ddTHH:mm:ssZ format. Example: "2014-12-01T12:00:00.000Z".
    :param conditions: The policy conditions used to limit the values that can be set in the upload form.
    :param policy_extra_props: Additional policy parameters. If new parameters are added to the policy in the future, you can pass them in as a dictionary.
    :return: signature, the signature string.
    """
    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 = {
        # Validity period.
        "expiration": generate_expiration(expire_time),
        # Constraints.
        "conditions": [
            # If success_action_redirect is not specified, the status code returned after a successful upload is 204 by default.
            ["eq", "$success_action_status", "200"],
            # The value of a form field must start with a specified prefix. For example, to specify that the value of key must start with user/user1, you can set this to ["starts-with", "$key", "user/user1"].
            ["starts-with", "$key", upload_dir],
            # Limit the minimum and maximum allowed size of the uploaded object in bytes.
            ["content-length-range", 1, 1000000],
            # Limit the uploaded files to the specified image types.
            ["in", "$content-type", ["image/jpeg", "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
        # You can add other parameters here as needed.
    }
    return json.dumps(response)

Go

package main

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

var (
    // Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
    accessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    // Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
    accessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    // The host is in the format of bucketname.endpoint. Replace ${your-bucket} with the bucket name. Replace ${your-endpoint} with the OSS endpoint, for example, oss-cn-hangzhou.aliyuncs.com.
    host = "http://${your-bucket}.${your-endpoint}"
    // Specify the prefix of the file to upload to OSS.
    uploadDir = "user-dir-prefix/"
    // Specify the expiration time in seconds.
    expireTime = int64(3600)
)

type ConfigStruct struct {
    Expiration string     `json:"expiration"`
    Conditions [][]string `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
    var condition []string
    condition = append(condition, "starts-with")
    condition = append(condition, "$key")
    condition = append(condition, uploadDir)
    config.Conditions = append(config.Conditions, condition)
    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));
}

// Obtain access credentials from environment variables. Before you run this sample code, make sure that the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables are configured.
$accessKeyId = getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
$accessKeySecret = getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// The format of $host is <YOUR-BUCKET>.<YOUR-ENDPOINT>. Replace them with your actual information.
$host = 'http://<YOUR-BUCKET>.<YOUR-ENDPOINT>';
// The prefix specified when a user uploads a file.
$dir = 'user-dir-prefix/';

$now = time();
//Set the timeout period of this policy to 10s. This means that after this validity period, the policy cannot be used for access.
$expire = 30;
$end = $now + $expire;
$expiration = gmt_iso8601($end);

//The maximum file size. You can set this as needed.
$condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000);
$conditions[] = $condition;

// The data uploaded by the user must start with $dir. Otherwise, the upload fails. This step is optional but recommended for security reasons to prevent users from uploading files to other users' directories through the 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);

Ruby

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

# Set the path of the public folder to the templates folder in the current directory.
set :public_folder, File.dirname(__FILE__) + '/templates'

# Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_ID']
# Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_SECRET']

# $host is in the format of <bucketname>.<endpoint>. Replace them with your actual information.
$host = 'http://<bucketname>.<endpoint>';

# The prefix specified when a user uploads a file.
$upload_dir = 'user-dir-prefix/'
# The expiration time in seconds.
$expire_time = 30
$server_ip = "0.0.0.0"
$server_port = 8000

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

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

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;
        // Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // The host is in the format of bucketname.endpoint. Replace <YOUR-BUCKET> with the bucket name. Replace <YOUR-ENDPOINT> with the OSS endpoint, for example, oss-cn-hangzhou.aliyuncs.com.
        public string Host { get; set; } = "<YOUR-BUCKET>.<YOUR-ENDPOINT>";
        // Specify the prefix of the file to upload to OSS.
        public string UploadDir { get; set; } = "user-dir-prefix/";
        // Specify the expiration time in seconds.
        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)));
            }
        }
    }
}

Client side

The browser client fetches the signature parameters from the server, assembles a FormData object, and POSTs it directly to the OSS bucket endpoint.

JavaScript

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("Failed to obtain the signature");
      }
      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("Upload successful");
        alert("The file is uploaded");
      } else {
        console.log("Upload failed", response);
        alert("Upload failed. Please try again later.");
      }
    })
    .catch((error) => {
      console.error("An error occurred:", error);
    });
});

Method 3: Signed URL for PutObject

How it works

  1. The browser client requests a signed URL from the application server.

  2. The server uses an OSS SDK to generate a signed URL for a PUT request and returns it to the client.

  3. The client uses the signed URL to call PutObject and upload the file directly.

  4. OSS returns a success response.

Signed URL upload flow
Important

The Content-Type set when generating the signed URL on the server must match the Content-Type sent by the browser client. A mismatch causes a SignatureDoesNotMatch error. For details, see How do I set the Content-Type (MIME)?.

This method is not suitable for multipart or resumable uploads. Generating a separate signed URL for each part on the server increases round-trips and complexity, and a client that modifies part content or order can produce a corrupted merged file.

Sample code

The following snippets show the core logic. For the complete runnable project, download presignedurl.zip.

Server side

The server exposes a GET /get_presigned_url_for_oss_upload endpoint. All examples use V4 signatures and return a signed URL that the client can use with a PUT request.

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 {

    /**
     * Configure the OSS endpoint, for example, oss-cn-hangzhou.aliyuncs.com.
     */
    private static final String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";

    /**
     * Set accessKeyId using the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
     */
    @Value("${ALIBABA_CLOUD_ACCESS_KEY_ID}")
    private String accessKeyId;

    /**
     * Set accessKeySecret using the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
     */
    @Value("${ALIBABA_CLOUD_ACCESS_KEY_SECRET}")
    private String accessKeySecret;

    private OSS ossClient;


    @Bean
    public OSS getSssClient() {
        // Specify the region where the bucket is located. For example, if the bucket is in the China (Hangzhou) region, set Region to cn-hangzhou.
        String region = "cn-hangzhou";
         // Create an OSSClient instance.
         // When the OSSClient instance is no longer used, call the shutdown method to release resources.
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
        .endpoint(endpoint)
        .credentialsProvider(credentialsProvider)
        .clientConfiguration(clientBuilderConfiguration)
        .region(region)
        .build();
        return ossClient;
    }

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

@Controller
public class PresignedURLController {

    /**
     * Replace <your-bucket> with the bucket name.
     * Specify the prefix of the file to upload to OSS.
     * Replace <your-object> with the full path of the object, for example, exampleobject.txt. The full path cannot contain the bucket name.
     * Specify the expiration time in milliseconds.
     */
    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;
    }
}

Node.js

const express = require("express");
const OSS = require("ali-oss");
const app = express();

app.get("/get_presigned_url_for_oss_upload", async (req, res) => {
 const client = new OSS({
   // Obtain the values of AccessKey ID, AccessKey secret, and STS token from environment variables.
   accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
   accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
   stsToken: process.env.ALIBABA_CLOUD_SECURITY_TOKEN,
   // Set yourBucketName to the bucket name.
   bucket: 'yourBucket',
   region: 'yourRegion',
   authorizationV4: true,
  });

  return await client.signatureUrlV4('PUT', 3600, {
    // Set the request headers based on the actual request headers that you send.
    headers: {},
  }, 'demo.pdf');
});

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

Python

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# Obtain access credentials from environment variables. Before you run this sample code, make sure that the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables are configured.
auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
# Specify the endpoint of the region where the bucket is located. For example, if the bucket is in the China (Hangzhou) region, set the endpoint to https://oss-cn-hangzhou.aliyuncs.com.
endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
# Specify the region that corresponds to the endpoint, for example, cn-hangzhou. Note that this parameter is required for V4 signatures.
region = "cn-hangzhou"

# Set examplebucket to the bucket name.
bucket = oss2.Bucket(auth, endpoint, "examplebucket", region=region)

# Specify an expiration time of 3,600s. The maximum expiration time is 32,400s.
expire_time = 3600
# Specify the full path of the object, for example, exampledir/exampleobject.png. The full path cannot contain the bucket name.
object_name = 'exampledir/exampleobject.png'

def generate_presigned_url():
    # Specify the header.
    headers = dict()
    # Specify the Content-Type.
    headers['Content-Type'] = 'image/png'
    # Specify the storage class.
    # headers["x-oss-storage-class"] = "Standard"
    # When a signed URL is generated, OSS escapes the forward slashes (/) in the full path of the object by default. As a result, the generated signed URL cannot be directly used.
    # Set slash_safe to True. This way, OSS does not escape the forward slashes (/) in the full path of the object, and the generated signed URL can be directly used.
    url = bucket.sign_url('PUT', object_name, expire_time, slash_safe=True, headers=headers)
    return url

Go

package main

import (
	"fmt"
	"net/http"
	"os"
        "log"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
)

func getURL() string {
	// Set yourEndpoint to the endpoint of the bucket. For example, if the bucket is in the China (Hangzhou) region, set the endpoint to https://oss-cn-hangzhou.aliyuncs.com. For other regions, specify the actual endpoints.
	endpoint := "https://oss-cn-hangzhou.aliyuncs.com"
	// Specify the bucket name, for example, examplebucket.
	bucketName := "examplebucket"
	// Specify the full path of the file, for example, exampledir/exampleobject.txt. The full path cannot contain the bucket name.
	objectName := "exampledir/exampleobject.txt"
	// Check whether the environment variables are configured.
	if endpoint == "" || bucketName == "" {
		log.Fatal("Please set yourEndpoint and bucketName.")
	}
	// Obtain access credentials from environment variables. Before you run this sample code, make sure that the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables are configured.
	provider, err := oss.NewEnvironmentVariableCredentialsProvider()
	if err != nil {
		handleError(err)
	}
        clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
	clientOptions = append(clientOptions, oss.Region("yourRegion"))
	// Set the signature version.
	clientOptions = append(clientOptions, oss.AuthVersion(oss.AuthV4))
        client, err := oss.New(endpoint, "", "", clientOptions...)

	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)
}

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

# Set the path of the public folder to the templates folder in the current directory.
set :public_folder, File.dirname(__FILE__) + '/templates'

# Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_KEY_ID']
# Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET']

# Specify the full path of the object, for example, exampledir/exampleobject.png. The full path cannot contain the bucket name.
object_key = 'exampledir/exampleobject.png'

def get_presigned_url(client, object_key)
  # Replace <YOUR-BUCKET> with the bucket name.
  bucket = client.get_bucket('<YOUR-BUCKET>')
  # Generate a signed URL and specify a validity period of 3,600s. The maximum validity period is 32,400s.
  bucket.object_url(object_key, 3600)
end

client = Aliyun::OSS::Client.new(
  # Replace <YOUR-ENDPOINT> with the endpoint of the region where the bucket is located. For example, if the bucket is in the China (Hangzhou) region, set the endpoint to https://oss-cn-hangzhou.aliyuncs.com.
  endpoint: '<YOUR-ENDPOINT>',
  # Obtain access credentials from environment variables. Before you run this sample code, make sure that the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables are configured.
  access_key_id: $access_key_id,
  access_key_secret: $access_key_secret
)

$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;

        // Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // Replace <YOUR-ENDPOINT> with the endpoint of the region where the bucket is located. For example, if the bucket is in the China (Hangzhou) region, set the endpoint to https://oss-cn-hangzhou.aliyuncs.com.
        private string EndPoint { get; set; } = "<YOUR-ENDPOINT>";
        // Replace <YOUR-BUCKET> with the bucket name.
        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();

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

            app.UseStaticFiles(); // Add this line to enable the static file middleware.

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

                // Print the log.
                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}"); // Print the value of the token.
                await context.Response.WriteAsync(signedUrl);
            });

            app.Run();
        }

        // Inject ILogger in the constructor.
        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }

        private string GetSignedUrl()
        {
            // Create an OssClient instance.
            var ossClient = new OssClient(EndPoint, AccessKeyId, AccessKeySecret);

            // Generate a signed 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();
        }
    }
}

Client side

The browser client fetches the signed URL from the server and uploads the file with a PUT request, setting the same Content-Type used to generate the URL.

JavaScript

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("Failed to obtain the presigned URL");
      }
      return response.text();
    })
    .then((url) => {
      const formData = new FormData();
      formData.append("file", file);
      fetch(url, {
        method: "PUT",
        headers: new Headers({
          "Content-Type": "image/png",
        }),
        body: file,
      }).then((response) => {
        if (!response.ok) {
          throw new Error("Failed to upload the file to OSS");
        }
        console.log(response);
        alert("The file is uploaded");
      });
    })
    .catch((error) => {
      console.error("An error occurred:", error);
      alert(error.message);
    });
});

Security best practices

Apply the following practices when implementing any of the three authorization methods:

  • Use HTTPS. Always serve your application server over HTTPS. Credentials and signed URLs transmitted over HTTP can be intercepted.

  • Set short expiration times. Use the shortest expiration time that satisfies your use case. Shorter-lived credentials and URLs reduce the window of exposure if they are intercepted or leaked.

  • Cache STS credentials; do not request new ones per upload. Reuse credentials until 60 seconds before expiry, then refresh. Calling AssumeRole on every upload triggers STS throttling.

  • Restrict credential scope with an inline policy. By default, STS credentials inherit all permissions of the RAM role. Attach an inline policy that limits operations to oss:PutObject on the target bucket and prefix.

  • Use a directory prefix. Set the dir field in PostPolicy or use key prefixes in signed URLs to prevent clients from writing to paths outside their designated directory.

  • Understand the minimum expiry rule. When you generate a signed URL using STS credentials, the effective expiry is the shorter of the two values. Set the STS credential duration to cover the full upload session, not just a single request.

References