すべてのプロダクト
Search
ドキュメントセンター

Object Storage Service:Browser.js SDK:アクセスの認可

最終更新日:Aug 27, 2026

このトピックでは、ブラウザ側のクライアントに、Object Storage Service (OSS) へオブジェクトをアップロードするための一時的な権限を付与する 3 つの方法 (Security Token Service (STS) が発行する一時的なアクセス認証情報、PostPolicy を使用した POST 署名、および署名付き URL) について説明します。いずれの方法でも、アプリケーションサーバーは長期的な AccessKey ペアを保持し、ブラウザはファイルを OSS に直接アップロードします。

前提条件

ブラウザから直接アップロードするには、サンプルコードを実行する前にリソースを作成しておく必要があります。

  • バケットの CORS ルール — アップロードフォームをホストするページと OSS エンドポイントはオリジンが異なるため、ブラウザはアップロードの前にプリフライトリクエストを送信します。ページを配信するオリジンを許可するために、宛先バケットで CORS ルールを設定します。CORS が設定されていない場合、リクエストが OSS に到達する前に、ブラウザによってアップロードがブロックされ、No 'Access-Control-Allow-Origin' header エラーが表示されます。

  • STS 用の RAM ロール — 方法 1 では AssumeRole 操作を呼び出します。この操作には、宛先バケットにオブジェクトをアップロードする権限を持つ RAM ロールが必要です。RAM ロールの詳細ページでロール ARN を取得し、サンプルコードに <YOUR-ROLE-ARN> が含まれている箇所で、この ARN を使用します。

  • サーバー側のアクセス資格情報 — アプリケーションサーバーは、長期的な AccessKey ペアを使用して STS と OSS を呼び出します。 サーバーを起動する前に、サーバー上で ALIBABA_CLOUD_ACCESS_KEY_ID および ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。 Python 用 OSS SDK は、代わりに OSS_ACCESS_KEY_ID および OSS_ACCESS_KEY_SECRET から AccessKey ペアを読み取ります。影響を受けるスニペットでは、この旨が記載されています。

  • 認証情報エンドポイントのアクセス制御 — サンプルサーバーは、/get_sts_token_for_oss_upload、/get_post_signature_for_oss_upload、および /get_presigned_url_for_oss_upload を、認証を必要としない GET エンドポイントとして公開します。これらのエンドポイントのいずれかにアクセスすると、誰でもお使いのバケットへの書き込みアクセス権を取得できてしまいます。本番環境では、お使いのアプリケーションのログインおよび認可チェックをエンドポイントに適用し、発行する認証情報の権限を制限してください。

  • アップロードフォームをホストするページ — すべてのクライアント側のサンプルでは、ページに ID が file の form 要素と file 入力が必要です。方法 1 ではさらに、Browser.js SDK が提供するグローバルな OSS オブジェクトが必要です。両方とも、以降のセクションでリンクされているサンプルプロジェクト内の templates/index.html に含まれています。

認可方法

OSS は、クライアントを認可するための複数のメソッドを提供しています。各メソッドでは、長期的な AccessKey ペアをアプリケーションサーバー上に保持し、ブラウザには短期間有効な認証情報、署名、または URL のみを渡します。次の表では、本トピックでサンプルコードを提供する 3 つのメソッドを比較します。認証および認可の要件に基づいて、メソッドを選択してください。

認可方式

クライアント側 OSS SDK

マルチパートアップロードと再開可能なアップロード

サーバー側で適用可能な制限

一般的なシナリオ

メソッド 1:サーバーで STS を使用して一時的なアクセス認証情報を生成 (推奨)

必要です。クライアントは認証情報を使用してリクエストに署名します。

サポートされています。クライアントは認証情報を再利用して各パートに署名します。

認証情報に追加のアクセスポリシーを関連付けることで、権限がさらに制限されます。

大容量ファイルを含むほとんどのアップロードシナリオ。

メソッド 2:サーバーで PostObject 用の署名と PostPolicy を生成

不要です。クライアントは HTML フォームを送信します。

サポートされていません。

PostPolicy により、サイズやタイプなど、アップロードするファイルのプロパティが制限されます。

アップロードするファイルのプロパティを制限する必要があるフォームベースのアップロード。

メソッド 3:サーバーで PutObject 用の署名付き URL を生成

不要です。クライアントは URL に対して PUT リクエストを送信します。

サポートされていません。

署名付き URL により、オブジェクト名と、Content-Type などの署名対象リクエストヘッダーが固定されます。

単一のオブジェクトのシンプルなアップロード。

方法1:サーバーで STS を使用して一時的なアクセス認証情報を生成する

重要

STSの一時アクセス資格情報と署名付きURLに有効期間を指定する必要があります。 一時的なアクセス資格情報を使用して、オブジェクトのアップロードやダウンロードなどの操作を実行するために使用される署名付きURLを生成する場合、最小有効期間が優先されます。 たとえば、一時的なアクセス資格情報の有効期間を1,200秒に設定し、資格情報を使用して生成された署名付きURLの有効期間を3,600秒に設定できます。 この場合、STSの一時アクセス資格情報の有効期限が切れた後は、署名付きURLを使用してオブジェクトをアップロードすることはできません。

次の図は、サーバーが一時的な STS アクセス認証情報を使用して、クライアントに OSS へのファイルアップロードを認可するプロセスを示しています。

image

このプロセスは、次の手順で構成されています。

  1. クライアントがアプリケーションサーバーに一時的なアクセス認証情報をリクエストします。

  2. アプリケーションサーバーは、STS SDK を使用して AssumeRole API を呼び出し、一時的なアクセス認証情報を取得します。

  3. STS は一時的なアクセス認証情報を生成し、アプリケーションサーバーに返します。

  4. アプリケーションサーバーは一時的なアクセス認証情報をクライアントに返します。

  5. クライアントは、OSS SDK と一時的なアクセス認証情報を使用して、ファイルを OSS にアップロードします。

  6. OSS は成功のレスポンスをクライアントに返します。

説明

STS サービスを頻繁に呼び出すと、スロットリングが発生する可能性があります。一時的な STS 認証情報をキャッシュし、期限切れになる前に更新することを推奨します。クライアントが一時的な STS アクセス認証情報を不正に使用するのを防ぐために、認証情報に追加のアクセスポリシーを付与して、その権限をさらに制限することを推奨します。

サンプルコード

以降のセクションでは、主要なスニペットを紹介します。完全なコードについては、サンプルプロジェクト:「sts.zip」をご参照ください。

サーバーサイドのサンプルコード

資格情報を、AccessKeyId、AccessKeySecret、SecurityToken、および Expiration フィールドを含むフラットな JSON オブジェクトとしてクライアントに返します。クライアント側のサンプルコードは Expiration を読み取って、いつ新しい資格情報を要求するかを決定するため、レスポンスにこれを含める必要があります。

以下の各スニペットは、クライアントサイドのサンプルコードがリクエストするパスである /get_sts_token_for_oss_upload で認証情報を返します。Python と PHP のスニペットには認証情報生成ロジックのみが含まれているため、サーバーが使用する Web フレームワークでそのパスにマウントしてください。

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 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() {
        //  を、指定された OSS バケットにファイルをアップロードする権限を持つ RAM ロールの ARN に置き換えます。ロールの ARN は、RAM ロールの詳細ページで取得できます。
        //  を、カスタムセッション名 (例:my-website-server) に設定します。
        AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest()
            .setDurationSeconds(3600L)
            .setRoleSessionName("<YOUR-ROLE-SESSION-NAME>")
            .setRoleArn("<YOUR-ROLE-ARN>");
        RuntimeOptions runtime = new RuntimeOptions();
        try {
            AssumeRoleResponse response = stsClient.assumeRoleWithOptions(assumeRoleRequest, runtime);
            // 認証情報には、AccessKeyId、AccessKeySecret、SecurityToken、および Expiration フィールドが含まれます。
            return response.body.credentials;
        } catch (TeaException error) {
            // 必要に応じてエラーを出力します。
            assertAsString(error.message);
            return null;
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 必要に応じてエラーを出力します。
            assertAsString(error.message);
            return null;
        }
    }
}

別のファイルで、STS クライアントを Spring Bean として宣言します。

import com.aliyun.sts20150401.Client;
import com.aliyun.teaopenapi.models.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class StsClientConfiguration {

    @Bean
    public Client stsClient() {
        // 認証情報クライアントを初期化する際にパラメーターを渡さない場合、Credentials ツールはデフォルトの認証情報プロバイダーチェーンを使用してクライアントを初期化します。
        Config config = new Config();
        //  を STS サービスのリージョン ID (例:ap-southeast-1) に置き換えます。
        config.endpoint = "sts.<YOUR-REGION>.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")));
// ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
const accessKeyId = process.env.ALIBABA_CLOUD_ACCESS_KEY_ID;
// ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
const accessKeySecret = process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;

app.get('/get_sts_token_for_oss_upload', (req, res) => {
  let sts = new STS({
   accessKeyId: accessKeyId,
   accessKeySecret: accessKeySecret
 });
   //  を、前提条件で作成した RAM ロールの ARN (例:acs:ram::175708322470****:role/ramtest) に置き換えます。
   // policy には、一時的な STS アクセス認証情報の権限をさらに制限するカスタムアクセスポリシーを設定します。ポリシーを指定しない場合、返される一時的な STS アクセス認証情報は、デフォルトで指定されたロールのすべての権限を持ちます。
   // 3600 は有効期間 (秒) です。
   // sessionName には、異なるトークンを区別するためのカスタムロールセッション名 (例:sessiontest) を指定します。
   sts.assumeRole('<YOUR-ROLE-ARN>', ``, '3600', '<YOUR-ROLE-SESSION-NAME>').then((result) => {
     console.log(result);
     // 認証情報とともに Expiration を返します。クライアントはこれを使用して、認証情報が期限切れになる前に更新します。
     res.json({
       AccessKeyId: result.credentials.AccessKeyId,
       AccessKeySecret: result.credentials.AccessKeySecret,
       SecurityToken: result.credentials.SecurityToken,
       Expiration: result.credentials.Expiration,
     });
   }).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

#  を、指定された OSS バケットにファイルをアップロードする権限を持つ RAM ロールの ARN に置き換えます。
role_arn_for_oss_upload = '<YOUR-ROLE-ARN>'
#  を STS サービスのリージョン ID (例:ap-southeast-1) に置き換えます。
region_id = '<YOUR-REGION>'

def get_sts_token():
    # CredentialClient を初期化する際にパラメーターを指定しない場合、デフォルトの認証情報プロバイダーチェーンが使用されます。
    # コンピューターでプログラムを実行する場合、ALIBABA_CLOUD_ACCESS_KEY_ID と ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を使用して AccessKey ペアを指定できます。
    # ECS、ECI、またはコンテナーサービスでプログラムを実行する場合、ALIBABA_CLOUD_ECS_METADATA 環境変数を使用してアタッチされたインスタンス RAM ロールを指定できます。SDK は自動的に一時的な STS 認証情報を取得します。
    config = Config(region_id=region_id, credential=CredentialClient())
    sts_client = Sts20150401Client(config=config)
    assume_role_request = sts_20150401_models.AssumeRoleRequest(
        role_arn=role_arn_for_oss_upload,
        #  をカスタムロールセッション名 (例:oss-role-session) に設定します。
        role_session_name='<YOUR-ROLE-SESSION-NAME>'
    )
    response = sts_client.assume_role(assume_role_request)
    # シリアル化された認証情報には、AccessKeyId、AccessKeySecret、SecurityToken、および Expiration フィールドが含まれます。
    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"
)

/**
 * AccessKey ペアを使用してアカウントクライアントを初期化します。
 * @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 シークレット。
        AccessKeySecret: accessKeySecret,
    }
    //  を STS サービスのリージョン ID (例:ap-southeast-1) に置き換えます。エンドポイントの詳細については、「https://api.alibabacloud.com/product/Sts」をご参照ください。
    config.Endpoint = tea.String("sts.<YOUR-REGION>.aliyuncs.com")
    return sts20150401.NewClient(config)
}

func AssumeRole(client *sts20150401.Client) (*sts20150401.AssumeRoleResponse, error) {
    assumeRoleRequest := &sts20150401.AssumeRoleRequest{
        DurationSeconds: tea.Int64(3600),
        //  を、指定された OSS バケットにファイルをアップロードする権限を持つ RAM ロールの ARN (例:acs:ram::175708322470****:role/ramtest) に置き換えます。
        RoleArn: tea.String("<YOUR-ROLE-ARN>"),
        //  をカスタムロールセッション名 (例:oss-role-session) に設定します。
        RoleSessionName: tea.String("<YOUR-ROLE-SESSION-NAME>"),
    }
    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)
        }
        // クライアントがフラットな JSON オブジェクトを受け取るように、認証情報のみをシリアル化します。
        responseBytes, err := json.Marshal(assumeRoleResponse.Body.Credentials)
        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\Sts\Sts;
// Alibaba Cloud クライアントを初期化します。
AlibabaCloud::accessKeyClient(getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'), getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'))
    //  を STS サービスのリージョン ID (例:ap-southeast-1) に置き換えます。
    ->regionId('<YOUR-REGION>')
    ->asDefaultClient();
// STS リクエストを作成します。
$request = Sts::v20150401()->assumeRole();
// STS リクエストを送信し、結果を取得します。
//  をカスタムロールセッション名 (例:oss-role-session) に設定します。
//  を、指定された OSS バケットにファイルをアップロードする権限を持つ RAM ロールの ARN に置き換えます。
$result = $request
    ->withRoleSessionName("<YOUR-ROLE-SESSION-NAME>")
    ->withDurationSeconds(3600)
    ->withRoleArn("<YOUR-ROLE-ARN>")
    ->request();
// STS リクエストの結果から認証情報を取得します。
$credentials = $result->get('Credentials');
// 返すためのフラットな JSON データを構築します。Expiration により、クライアントは認証情報が期限切れになる前に更新できます。
$response = [
    'AccessKeyId' => $credentials['AccessKeyId'],
    'AccessKeySecret' => $credentials['AccessKeySecret'],
    'SecurityToken' => $credentials['SecurityToken'],
    'Expiration' => $credentials['Expiration'],
];
// レスポンスヘッダーを application/json に設定します。
header('Content-Type: application/json');
// 結果を JSON 形式に変換して出力します。
echo json_encode($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,
                //  を STS サービスのリージョン ID (例:ap-southeast-1) に置き換えます。
                Endpoint = "sts.<YOUR-REGION>.aliyuncs.com"
            };
            return new AlibabaCloud.SDK.Sts20150401.Client(config);
        }
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // アプリケーションをビルドする前にサービスを登録します。Build が呼び出された後、サービスコレクションは読み取り専用になります。
            builder.Logging.AddConsole();
            var app = builder.Build();
            var logger = app.Services.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 client = CreateClient(Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID"), Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET"));
                var assumeRoleRequest = new AlibabaCloud.SDK.Sts20150401.Models.AssumeRoleRequest();
                //  をカスタムロールセッション名 (例:oss-role-session) に設定します。
                assumeRoleRequest.RoleSessionName = "<YOUR-ROLE-SESSION-NAME>";
                //  を、指定された OSS バケットにファイルをアップロードする権限を持つ 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;
        }
    }
}

Ruby

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

# public フォルダーのパスをカレントディレクトリの templates フォルダーに設定します。
set :public_folder, File.dirname(__FILE__) + '/templates'

def get_sts_token_for_oss_upload()
  client = RPCClient.new(
    # ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
    access_key_id: ENV['ALIBABA_CLOUD_ACCESS_KEY_ID'],
    # ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
    access_key_secret: ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET'],
    #  を、STS を呼び出すリージョンの ID (例:ap-southeast-1) に置き換えます。
    endpoint: 'https://sts.<YOUR-REGION>.aliyuncs.com',
    api_version: '2015-04-01'
  )
  response = client.request(
    action: 'AssumeRole',
    params: {
      #  を、前提条件で作成した RAM ロールの ARN (例:acs:ram::175708322470****:role/ramtest) に置き換えます。
      "RoleArn": "<YOUR-ROLE-ARN>",
      # 3600 は有効期間 (秒) です。
      "DurationSeconds": 3600,
      # RoleSessionName には、異なるトークンを区別するためのカスタムロールセッション名 (例:sessiontest) を指定します。
      "RoleSessionName": "<YOUR-ROLE-SESSION-NAME>"
    },
    opts: {
      method: 'POST',
      format_params: true
    }
  )
end

$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}"

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

get '/get_sts_token_for_oss_upload' do
  token = get_sts_token_for_oss_upload()
  # 認証情報とともに Expiration を返します。クライアントはこれを使用して、認証情報が期限切れになる前に更新します。
  response = {
    "AccessKeyId" => token["Credentials"]["AccessKeyId"],
    "AccessKeySecret" => token["Credentials"]["AccessKeySecret"],
    "SecurityToken" => token["Credentials"]["SecurityToken"],
    "Expiration" => token["Credentials"]["Expiration"]
  }
  response.to_json
end

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

クライアントサイドのサンプルコード

次のサンプルコードは、一時的なアクセス認証情報を使用して Web クライアントから OSS にファイルをアップロードする方法を示しています。

let credentials = null;
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  // STS サービスへの呼び出し回数を減らすため、現在の認証情報が期限切れになった場合にのみ、一時的な認証情報を再取得します。
  if (isCredentialsExpired(credentials)) {
    const response = await fetch("/get_sts_token_for_oss_upload", {
      method: "GET",
    });
    if (!response.ok) {
      // エラーの HTTP ステータスコードを処理します。
      throw new Error(
        `Failed to obtain the STS token: ${response.status} ${response.statusText}`
      );
    }
    credentials = await response.json();
  }
  const client = new OSS({
    //  を OSS バケットの名前に設定します。
    bucket: "<YOUR-BUCKET>",
    //  を、OSS バケットが配置されているリージョンの ID (例:ap-southeast-1) に置き換えます。
    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);
});

/**
 * 一時的な認証情報が期限切れになったかどうかを確認します。
 **/
function isCredentialsExpired(credentials) {
  if (!credentials) {
    return true;
  }
  const expireDate = new Date(credentials.Expiration);
  const now = new Date();
  // 有効期間が1分以下の場合、認証情報は期限切れと見なされます。
  return expireDate.getTime() - now.getTime() <= 60000;
}

put メソッドは、OSS がオブジェクトを格納した後に解決され、サンプルでは結果がブラウザーコンソールにログ出力されます。 オブジェクトは、クライアント設定で指定したバケットに、ユーザーが選択したファイルの名前で格納されます。 アップロードが失敗した場合は、ブラウザーコンソールでエラーを確認し、前提条件で説明されているバケットの CORS ルールが、ページを配信するオリジンを許可していることを確認してください。

方法 2:サーバーでの PostObject 用の署名と Post ポリシーの生成

この方法では、ブラウザは HTML フォームを Object Storage Service (OSS) に直接送信します。クライアントは Browser.js SDK を使用せず、クライアントサイドのサンプルコードはブラウザの FormData API と fetch API のみに依存します。

次の図は、サーバーが Post 署名と Post ポリシーを使用して、クライアントに OSS へのファイルアップロードを認可するプロセスを示しています。

image

このプロセスは、次の手順で構成されています。

  1. クライアントが、Post 署名や Post ポリシーなどの情報をアプリケーションサーバーにリクエストします。

  2. アプリケーションサーバーが、Post 署名や Post ポリシーなどの情報を生成してクライアントに返します。

  3. クライアントが、Post 署名、Post ポリシー、およびその他の情報を使用して、HTML フォームから PostObject API を呼び出してファイルを OSS にアップロードします。

  4. OSS が成功のレスポンスをクライアントに返します。

    サーバーが生成する Post ポリシーは、クライアントがアップロードするファイルを制限します。たとえば、ファイルサイズやオブジェクト名のプレフィックスを制限できます。

重要

Post ポリシーの各条件には、クライアントが送信するフォーム内に一致するフィールドが必要です。条件が満たされない場合、OSS はアップロードを拒否します。

サンプルコード

以降のセクションでは、主要なスニペットを提供します。完全なコードについては、サンプルプロジェクト postsignature.zip をご参照ください。

サーバーサイドのサンプルコード

以下の各スニペットは、クライアント側のサンプルコードがアップロードフォームに追加する host、policy、signature、ossAccessKeyId、および dir フィールドを返します。これらのスニペットは、クライアント側のサンプルコードがリクエストするパスである /get_post_signature_for_oss_upload でこのレスポンスを提供します。Python と PHP のスニペットには署名生成ロジックのみが含まれているため、サーバーが使用する Web フレームワーク内のそのパスにマウントしてください。

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.JSONException;
import org.codehaus.jettison.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@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(StandardCharsets.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.");
            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 a 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());
        } catch (JSONException je) {
            System.out.println("Failed to construct the JSON response.");
            System.out.println("Error Message: " + je.getMessage());
        }
        // ここで注入された OSS クライアントをシャットダウンしないでください。これはすべてのリクエストで共有され、アプリケーションが停止したときに解放されます。
        return response.toString();
    }
}

別のファイルで、OSS クライアントとアップロード設定を Spring 設定クラスとして宣言します。

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;

@Configuration
public class OssConfig {
    /**
     *  を、バケットが配置されているリージョンのエンドポイント (例: oss-ap-southeast-1.aliyuncs.com) に置き換えます。
     */
    private String endpoint = "<YOUR-ENDPOINT>";
    /**
     *  をバケット名に置き換えます。
     */
    private String bucket = "<YOUR-BUCKET>";
    /**
     * OSS にアップロードするファイルのプレフィックスを指定します。
     */
    private String dir = "user-dir-prefix/";
    /**
     * 有効期間を秒単位で指定します。
     */
    private long expireTime = 3600;
    /**
     * ホストを構築します。
     */
    private String host = "https://" + bucket + "." + endpoint;
    /**
     * ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を使用して accessKeyId を設定します。
     */
    private String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
    /**
     * ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を使用して 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();
    }
}

Node.js

const express = require("express");
const { Buffer } = require("buffer");
const OSS = require("ali-oss");
const app = express();
const path = require("path");
const config = {
  // ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
  accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
  // ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
  accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
  //  をバケット名に置き換えます。
  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();
  // 署名の有効期間を秒単位で設定します。
  date.setSeconds(date.getSeconds() + 3600);
  const policy = {
    expiration: date.toISOString(),
    conditions: [
      // アップロードされるファイルのサイズ制限を設定します。
      ["content-length-range", 0, 1048576000],
      // ファイルをアップロードできるバケットを指定します。
      { bucket: client.options.bucket },
    ],
  };
  const formData = await client.calculatePostSignature(policy);
  const host = `https://${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

# ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
access_key_id = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID')
# ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
access_key_secret = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
#  をバケット名に置き換えます。
bucket = '<YOUR-BUCKET>'
# ホストは bucketname.endpoint 形式です。 をバケット名に置き換え、 を OSS エンドポイント (例: oss-ap-southeast-1.aliyuncs.com) に置き換えます。
host = 'https://<YOUR-BUCKET>.<YOUR-ENDPOINT>'
# OSS にアップロードするファイルのプレフィックスを指定します。
upload_dir = 'user-dir-prefix/'
# 有効期間を秒単位で指定します。
expire_time = 3600

def generate_expiration(seconds):
    """
    有効期間を秒単位で指定して有効期限を生成します。
    :param seconds: 有効期間 (秒)。
    :return: "2014-12-01T12:00:00.000Z" のような ISO 8601 形式の時刻文字列。
    """
    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: 宛先バケットへのアクセス権を持つ AccessKey Secret。
    :param expiration: 署名の有効期限。値は ISO 8601 標準に準拠し、UTC で、yyyy-MM-ddTHH:mm:ssZ 形式である必要があります。例: "2014-12-01T12:00:00.000Z"。
    :param conditions: アップロードフォームで許可される値を制限するポリシー条件。
    :param policy_extra_props: 追加のポリシーパラメーター。後でポリシーが新しいパラメーターをサポートする場合、追加のパラメーターを dict として渡すことができます。
    :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 が指定されていない場合、アップロード成功後に返されるステータスコードはデフォルトで 204 です。
            ["eq", "$success_action_status", "200"],
            # フォームフィールドの値は、指定されたプレフィックスで始まる必要があります。たとえば、key の値が user/user1 で始まるように指定するには、この条件を ["starts-with", "$key", "user/user1"] に設定します。
            ["starts-with", "$key", upload_dir],
            # アップロードされるオブジェクトの最小および最大許容サイズをバイト単位で制限します。
            ["content-length-range", 0, 1048576000]
            # クライアントサイドのフォームが一致するフィールドを送信する場合にのみ条件を追加します。そうしないと、OSS はアップロードを拒否します。
        ]
    }
    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
        # 必要に応じて、ここに追加のパラメーターを付加できます。
    }
    return json.dumps(response)

Go

package main

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

var (
    // ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
    accessKeyId = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
    // ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
    accessKeySecret = os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
    // ホストは bucketname.endpoint 形式です。 をバケット名に置き換え、 を OSS エンドポイント (例: oss-ap-southeast-1.aliyuncs.com) に置き換えます。
    host = "https://<YOUR-BUCKET>.<YOUR-ENDPOINT>"
    // OSS にアップロードするファイルのプレフィックスを指定します。
    uploadDir = "user-dir-prefix/"
    // 有効期間を秒単位で指定します。
    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));
}

// 環境変数からアクセス認証情報を取得します。このサンプルコードを実行する前に、ALIBABA_CLOUD_ACCESS_KEY_ID および ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数が設定されていることを確認してください。
$accessKeyId = getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
$accessKeySecret = getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
// ホストは bucketname.endpoint 形式です。 をバケット名に置き換え、 を OSS エンドポイント (例: oss-ap-southeast-1.aliyuncs.com) に置き換えます。
$host = 'https://<YOUR-BUCKET>.<YOUR-ENDPOINT>';
// ユーザーがファイルをアップロードするときに指定するプレフィックス。
$dir = 'user-dir-prefix/';          

$now = time();
// 有効期間を秒単位で指定します。有効期間が過ぎると、ポリシーはアクセスに使用できなくなります。
$expire = 3600;
$end = $now + $expire;
$expiration = gmt_iso8601($end);

//最大ファイルサイズ。この値は必要に応じて設定できます。
$condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000);
$conditions[] = $condition;

// ユーザーがアップロードするデータは $dir で始まる必要があります。そうでない場合、アップロードは失敗します。この設定はオプションであり、ユーザーがポリシーを使用して他のユーザーのディレクトリにファイルをアップロードするのを防ぐというセキュリティ上の目的で使用されます。
$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'

# public フォルダーのパスをカレントディレクトリの templates フォルダーに設定します。
set :public_folder, File.dirname(__FILE__) + '/templates'

# ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_KEY_ID']
# ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET']

# ホストは bucketname.endpoint 形式です。 をバケット名に置き換え、 を OSS エンドポイント (例: oss-ap-southeast-1.aliyuncs.com) に置き換えます。
$host = 'https://<YOUR-BUCKET>.<YOUR-ENDPOINT>'

# ユーザーがファイルをアップロードするときに指定するプレフィックス。
$upload_dir = 'user-dir-prefix/'
# 有効期間を秒単位で指定します。
$expire_time = 3600
$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_json(source_hash)
  json_string = source_hash.to_json

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

  json_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_json(policy_dict)
  policy_encode = Base64.strict_encode64(policy).chomp;
  h = OpenSSL::HMAC.digest('sha1', $access_key_secret, policy_encode)
  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_json(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;
        // ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        // ホストは bucketname.endpoint 形式です。 をバケット名に置き換え、 を OSS エンドポイント (例: oss-ap-southeast-1.aliyuncs.com) に置き換えます。
        public string Host { get; set; } = "https://<YOUR-BUCKET>.<YOUR-ENDPOINT>";
        // OSS にアップロードするファイルのプレフィックスを指定します。
        public string UploadDir { get; set; } = "user-dir-prefix/";
        // 有効期間を秒単位で指定します。
        public int ExpireTime { get; set; } = 3600;
        public class PolicyConfig
        {
            public string expiration { get; set; }
            public List<List<object>> conditions { get; set; }
        }
        public class PolicyToken
        {
            [JsonProperty("ossAccessKeyId")]
            public string OssAccessKeyId { get; set; }
            [JsonProperty("policy")]
            public string Policy { get; set; }
            [JsonProperty("signature")]
            public string Signature { get; set; }
            [JsonProperty("dir")]
            public string Dir { get; set; }
            [JsonProperty("host")]
            public string Host { get; set; }
            [JsonProperty("expire")]
            public string Expire { get; set; }
        }
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // アプリケーションをビルドする前にサービスを登録します。Build が呼び出された後、サービスコレクションは読み取り専用になります。
            builder.Logging.AddConsole();
            var app = builder.Build();
            var logger = app.Services.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
            {
                OssAccessKeyId = 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)));
            }
        }
    }
}

クライアントサイドのサンプルコード

次のサンプルコードは、Post 署名や Post ポリシーなどの情報を使用して、Web クライアントから 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("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("The upload failed. Try again later.");
      }
    })
    .catch((error) => {
      console.error("An error occurred:", error);
    });
});

方法3:サーバーでの PutObject 用署名付き URL の生成

この方法では、ブラウザはサーバーで署名された URL に PUT リクエストを送信します。クライアントは Browser.js SDK を使用せず、クライアントサイドのサンプルコードはブラウザの fetch API のみを使用します。

次の図は、サーバーが署名付き URL を使用して、クライアントが OSS にファイルをアップロードすることを許可するプロセスを示しています。

image

このプロセスは、次の手順で構成されています。

  1. クライアントがアプリケーションサーバーに署名付き URL をリクエストします。

  2. アプリケーションサーバーは、OSS SDK を使用して PUT リクエスト用の署名付き URL を生成し、その URL をクライアントに返します。

  3. クライアントは、PUT リクエスト用の署名付き URL を使用して、PutObject API を呼び出してファイルを OSS にアップロードします。

  4. OSS が成功レスポンスをクライアントに返します。

    この方法は、大きなファイルのマルチパートアップロードや、マルチパートアップロードに基づくレジューム可能なアップロードには適していません。サーバーで各パートの署名付き URL を生成してクライアントに返すと、サーバーとのやり取りの回数が増え、ネットワークリクエストがより複雑になります。さらに、クライアントがパートの内容や順序を変更する可能性があり、最終的にマージされたオブジェクトが不正になる原因となります。

重要

オプションのパラメーターを含む署名付き URL をフロントエンドで使用する場合は、サーバーによる署名付き URL 生成時の Content-Type と、フロントエンドでの URL 使用時の Content-Type が同じであることを確認してください。そうでない場合、SignatureDoesNotMatch エラーが発生する可能性があります。Content-Type の設定方法の詳細については、「Content-Type (MIME) の設定方法」をご参照ください。

サンプルコード

以降のセクションでは、主要なコードスニペットを提供します。完全なコードについては、サンプルプロジェクト: 「presignedurl.zip」をご参照ください。

サーバーサイドのサンプルコード

以下の各スニペットは、クライアント側のサンプルコードがリクエストするパスである /get_presigned_url_for_oss_upload で、署名付き URL をプレーンテキストとして返します。これらのスニペットは image/png Content-Type に署名します。これはクライアント側のサンプルコードが送信する Content-Type と一致します。 Python スニペットには URL 生成ロジックのみが含まれているため、サーバーが使用する Web フレームワークでそのパスにマウントしてください。 Ruby スニペットでは追加の変更が 1 つ必要です。これについては、Ruby タブの注意事項で説明されています。

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 {

    /**
     * OSS エンドポイントを設定します。例: https://oss-ap-southeast-1.aliyuncs.com
     */
    private static final String endpoint = "https://oss-ap-southeast-1.aliyuncs.com";

    /**
     * ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数から accessKeyId を設定します。
     */
    @Value("${ALIBABA_CLOUD_ACCESS_KEY_ID}")
    private String accessKeyId;

    /**
     * ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数から accessKeySecret を設定します。
     */
    @Value("${ALIBABA_CLOUD_ACCESS_KEY_SECRET}")
    private String accessKeySecret;

    private OSS ossClient;

    @Bean
    public OSS getOssClient() {
        // OSSClient インスタンスを作成します。
        // OSSClient インスタンスが不要になったら、shutdown メソッドを呼び出してリソースを解放します。
        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        return ossClient;
    }

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

@Controller
public class PresignedURLController {

    /**
     *  をバケット名に置き換えます。
     *  をオブジェクトのフルパス (例: exampledir/exampleobject.png) に置き換えます。オブジェクトのフルパスにバケット名を含めることはできません。
     * 有効期間をミリ秒単位で指定します。
     */
    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({
    // 環境変数からアクセス認証情報を取得します。このサンプルコードを実行する前に、ALIBABA_CLOUD_ACCESS_KEY_ID および ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数が設定されていることを確認してください。
    accessKeyId: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
    accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
    //  をバケット名に置き換えます。
    bucket: '<YOUR-BUCKET>',
    //  をバケットが配置されているリージョン (例: ap-southeast-1) に置き換えます。
    region: '<YOUR-REGION>',
    authorizationV4: true,
  });

  // クライアントが送信するのと同じ Content-Type に対して署名します。そうしないと、SignatureDoesNotMatch エラーが発生します。
  const url = await client.signatureUrlV4('PUT', 3600, {
    headers: {
      'Content-Type': 'image/png',
    },
  }, 'exampledir/exampleobject.png');

  // 署名付き URL をプレーンテキストとして返します。クライアントサイドのサンプルコードは、response.text() を呼び出してレスポンスボディを読み取ります。
  res.type('text/plain').send(url);
});

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

Python

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# 環境変数からアクセス認証情報を取得します。このサンプルコードを実行する前に、OSS_ACCESS_KEY_ID および OSS_ACCESS_KEY_SECRET 環境変数が設定されていることを確認してください。
auth = oss2.AuthV4(EnvironmentVariableCredentialsProvider())
# バケットが配置されているリージョンのエンドポイント (例: https://oss-ap-southeast-1.aliyuncs.com) を指定します。
endpoint = "https://oss-ap-southeast-1.aliyuncs.com"
# エンドポイントに対応するリージョン (例: ap-southeast-1) を指定します。このパラメーターは V4 署名で必須です。
region = "ap-southeast-1"

#  をバケット名に置き換えます。
bucket = oss2.Bucket(auth, endpoint, "<YOUR-BUCKET>", region=region)

# 3,600 秒の有効期間を指定します。最大有効期間は 32,400 秒です。
expire_time = 3600
# オブジェクトのフルパス (例: exampledir/exampleobject.png) を指定します。オブジェクトのフルパスにバケット名を含めることはできません。
object_name = 'exampledir/exampleobject.png'

def generate_presigned_url():
    # ヘッダーを指定します。
    headers = dict()
    # Content-Type を指定します。
    headers['Content-Type'] = 'image/png'
    # ストレージクラスを指定します。
    # headers["x-oss-storage-class"] = "Standard"
    # 署名付き URL の生成時、OSS はデフォルトでオブジェクトのフルパス内のスラッシュ (/) をエスケープします。その結果、生成された署名付き URL は直接使用できません。
    # slash_safe を True に設定すると、OSS はオブジェクトのフルパス内のスラッシュ (/) をエスケープせず、生成された署名付き URL を直接使用できます。
    url = bucket.sign_url('PUT', object_name, expire_time, slash_safe=True, headers=headers)
    return url

Go

package main

import (
    "fmt"
    "log"
    "net/http"

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

func getURL() string {
    // バケットが配置されているリージョンのエンドポイント (例: https://oss-ap-southeast-1.aliyuncs.com) を指定します。
    endpoint := "https://oss-ap-southeast-1.aliyuncs.com"
    //  をバケット名に置き換えます。
    bucketName := "<YOUR-BUCKET>"
    // オブジェクトのフルパス (例: exampledir/exampleobject.png) を指定します。オブジェクトのフルパスにバケット名を含めることはできません。
    objectName := "exampledir/exampleobject.png"
    // 環境変数からアクセス認証情報を取得します。このサンプルコードを実行する前に、ALIBABA_CLOUD_ACCESS_KEY_ID および ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数が設定されていることを確認してください。
    provider, err := oss.NewEnvironmentVariableCredentialsProvider()
    if err != nil {
        log.Fatal("Failed to obtain the access credentials: ", err)
    }
    clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
    //  をバケットが配置されているリージョン (例: ap-southeast-1) に置き換えます。
    clientOptions = append(clientOptions, oss.Region("<YOUR-REGION>"))
    // 署名バージョンを指定します。
    clientOptions = append(clientOptions, oss.AuthVersion(oss.AuthV4))
    client, err := oss.New(endpoint, "", "", clientOptions...)
    if err != nil {
        log.Fatal("Failed to create the OSSClient instance: ", err)
    }
    bucket, err := client.Bucket(bucketName)
    if err != nil {
        log.Fatal("Failed to obtain the bucket: ", err)
    }
    options := []oss.Option{
        oss.ContentType("image/png"),
    }
    // 3,600 秒の有効期間を指定します。
    signedURL, err := bucket.SignURL(objectName, oss.HTTPPut, 3600, options...)
    if err != nil {
        log.Fatal("Failed to generate the signed URL: ", 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

# public フォルダーのパスをカレントディレクトリの templates フォルダーに設定します。
set :public_folder, File.dirname(__FILE__) + '/templates'

# ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
$access_key_id = ENV['ALIBABA_CLOUD_ACCESS_KEY_ID']
# ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET']

# オブジェクトのフルパス (例: exampledir/exampleobject.png) を指定します。オブジェクトのフルパスにバケット名を含めることはできません。
object_key = 'exampledir/exampleobject.png'

def get_presigned_url(client, object_key)
  #  をバケット名に置き換えます。
  bucket = client.get_bucket('<YOUR-BUCKET>')
  # 次のプレースホルダーを、object_key に対する PUT リクエストに署名し、有効期間を指定する呼び出しに置き換えてください。最大有効期間は 32,400 秒です。
  '<YOUR-PUT-SIGNED-URL>'
end

client = Aliyun::OSS::Client.new(
  #  をバケットが配置されているリージョンのエンドポイント (例: https://oss-ap-southeast-1.aliyuncs.com) に置き換えます。
  endpoint: '<YOUR-ENDPOINT>',
  # ALIBABA_CLOUD_ACCESS_KEY_ID および ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数から読み取ったアクセス認証情報を渡します。
  access_key_id: $access_key_id,
  access_key_secret: $access_key_secret
)

$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}"

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
重要

このスニペットには、PUT リクエスト用の署名付き URL を生成する呼び出しは含まれていません。このスニペットを使用する前に、<YOUR-PUT-SIGNED-URL> プレースホルダーを、PUT リクエストに署名し、有効期間を設定する呼び出しに置き換えてください。そうしないと、クライアントからの PUT リクエストは OSS によって拒否され、SignatureDoesNotMatch または AccessDenied エラーが返されます。

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;

        // ALIBABA_CLOUD_ACCESS_KEY_ID 環境変数を設定します。
        public string AccessKeyId { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID");
        // ALIBABA_CLOUD_ACCESS_KEY_SECRET 環境変数を設定します。
        public string AccessKeySecret { get; set; } = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
        //  をバケットが配置されているリージョンのエンドポイント (例: https://oss-ap-southeast-1.aliyuncs.com) に置き換えます。
        private string EndPoint { get; set; } = "<YOUR-ENDPOINT>";
        //  をバケット名に置き換えます。
        private string BucketName { get; set; } = "<YOUR-BUCKET>";
        // オブジェクトのフルパスを指定します。オブジェクトのフルパスにバケット名を含めることはできません。
        private string ObjectName { get; set; } = "exampledir/exampleobject.png";

        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // ロギングを追加します。アプリケーションをビルドする前にサービスを登録します。Build が呼び出された後、サービスコレクションは読み取り専用になります。
            builder.Logging.AddConsole();
            var app = builder.Build();
            var logger = app.Services.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_presigned_url_for_oss_upload", async (context) =>
            {
                var program = new Program(logger);
                var signedUrl = program.GetSignedUrl();

                logger.LogInformation($"SignedUrl: {signedUrl}"); // 署名付き URL を出力します。
                await context.Response.WriteAsync(signedUrl);
            });

            app.Run();
        }

        // コンストラクターを使用して ILogger を注入します。
        public Program(ILogger<Program> logger)
        {
            _logger = logger;
        }

        private string GetSignedUrl()
        {
            // OSSClient インスタンスを作成します。
            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();
        }
    }
}

クライアントサイドのサンプルコード

次のサンプルコードは、署名付き URL を使用して、Web クライアントから 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("Failed to obtain the presigned URL");
      }
      return response.text();
    })
    .then((url) => {
      return 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);
    });
});

リファレンス

  • STS を使用して一時的なアクセスを付与する完全なサンプルコードについては、GitHub のサンプルをご参照ください。

  • 署名付き URL を使用して一時的なアクセスを付与する完全なサンプルコードについては、GitHub のサンプルをご参照ください。