Cette rubrique présente trois méthodes permettant d'accorder à un client côté navigateur une autorisation temporaire pour charger des objets vers Object Storage Service (OSS) : les identifiants d'accès temporaires émis par Security Token Service (STS), une signature Post avec une PostPolicy et une URL présignée. Quelle que soit la méthode choisie, votre serveur d'application conserve la paire AccessKey à long terme tandis que le navigateur charge le fichier directement vers OSS.
Méthodes d'autorisation
OSS prend en charge plusieurs méthodes pour autoriser les clients. Chaque méthode maintient la paire AccessKey à long terme sur le serveur d'application et ne transmet au navigateur qu'un identifiant, une signature ou une URL à durée de vie limitée. Le tableau suivant compare les trois méthodes pour lesquelles cette rubrique fournit des exemples de code. Sélectionnez une méthode en fonction de vos exigences en matière d'authentification et d'autorisation.
|
Méthode d'autorisation |
SDK OSS côté client |
Chargement multipartie et reprise |
Restrictions applicables par le serveur |
Scénario typique |
|
Méthode 1 : Utiliser STS sur le serveur pour générer des identifiants d'accès temporaires (recommandé) |
Requis. Le client signe ses propres requêtes à l'aide des identifiants. |
Pris en charge. Le client réutilise les identifiants pour signer chaque partie. |
Une politique d'accès supplémentaire associée aux identifiants limite davantage leurs autorisations. |
La plupart des scénarios de chargement, y compris les fichiers volumineux. |
|
Méthode 2 : Générer des signatures et des PostPolicies sur le serveur pour PostObject |
Non requis. Le client soumet un formulaire HTML. |
Non pris en charge. |
Une PostPolicy restreint les propriétés du fichier chargé, telles que sa taille et son type. |
Chargements basés sur des formulaires nécessitant de restreindre les propriétés des fichiers chargés. |
|
Méthode 3 : Générer des URL signées sur le serveur pour PutObject |
Non requis. Le client envoie une requête PUT à l'URL. |
Non pris en charge. |
L'URL signée fixe le nom de l'objet et les en-têtes de requête signés, tels que Content-Type. |
Chargements simples d'un objet unique. |
Méthode 1 : Utiliser STS sur le serveur pour générer des identifiants d'accès temporaires
Vous devez spécifier une période de validité pour les identifiants d'accès temporaires STS et pour une URL signée. Lorsque vous utilisez des identifiants d'accès temporaires pour générer une URL signée destinée à effectuer des opérations, telles que le chargement et le téléchargement d'objets, la période de validité minimale s'applique en priorité. Par exemple, vous pouvez définir la période de validité de vos identifiants d'accès temporaires à 1 200 secondes et celle de l'URL signée générée à l'aide de ces identifiants à 3 600 secondes. Dans ce cas, l'URL signée ne pourra pas être utilisée pour charger des objets après l'expiration des identifiants d'accès temporaires STS, même si l'URL signée est encore valide.
La figure suivante illustre le processus par lequel le serveur utilise des identifiants d'accès STS temporaires pour autoriser un client à charger des fichiers vers OSS.
Le processus comprend les étapes suivantes :
Le client demande des identifiants d'accès temporaires au serveur d'application.
Le serveur d'application utilise un SDK STS pour appeler l'opération AssumeRole afin d'obtenir des identifiants d'accès temporaires.
STS génère et renvoie les identifiants d'accès temporaires au serveur d'application.
Le serveur d'application renvoie les identifiants d'accès temporaires au client.
Le client utilise un SDK OSS et les identifiants d'accès temporaires pour charger un fichier vers OSS.
OSS renvoie une réponse de succès au client.
Des appels fréquents au service STS peuvent entraîner une limitation du débit. Nous vous recommandons de mettre en cache les identifiants STS temporaires et de les actualiser avant leur expiration. Pour empêcher le client d'utiliser de manière abusive les identifiants d'accès temporaires STS, nous vous conseillons d'ajouter une politique d'accès supplémentaire aux identifiants afin de limiter davantage leurs autorisations.
Exemple de code
Les sections suivantes fournissent des extraits de code essentiels. Pour obtenir le code complet, consultez l'exemple de projet : sts.zip.
Exemple de code côté serveur
Renvoyez les identifiants au client sous la forme d'un objet JSON plat contenant les champs AccessKeyId, AccessKeySecret, SecurityToken et Expiration. L'exemple de code côté client lit le champ Expiration pour déterminer quand demander de nouveaux identifiants ; la réponse doit donc inclure ce champ.
Chacun des extraits suivants sert les identifiants à l'adresse /get_sts_token_for_oss_upload, qui correspond au chemin demandé par l'exemple de code côté client. Les extraits Python et PHP contiennent uniquement la logique de génération des identifiants ; montez-les donc sur ce chemin dans le framework web utilisé par votre serveur.
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() {
// 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 on the details page of the RAM role.
// Set <YOUR-ROLE-SESSION-NAME> to a custom session name, for example, 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);
// The credentials contain the AccessKeyId, AccessKeySecret, SecurityToken, and Expiration fields.
return response.body.credentials;
} catch (TeaException error) {
// Print the error if needed.
assertAsString(error.message);
return null;
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// Print the error if needed.
assertAsString(error.message);
return null;
}
}
}
Déclarez le client STS en tant que bean Spring dans un fichier distinct :
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() {
// If you do not pass any parameters when you initialize the credential client, the Credentials tool initializes the client by using the default credential provider chain.
Config config = new Config();
// Replace <YOUR-REGION> with the region ID of the STS service, for example, 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")));
// 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_KEY_SECRET;
app.get('/get_sts_token_for_oss_upload', (req, res) => {
let sts = new STS({
accessKeyId: accessKeyId,
accessKeySecret: accessKeySecret
});
// Replace <YOUR-ROLE-ARN> with the ARN of the RAM role that you created in the prerequisites, for example, acs:ram::175708322470****:role/ramtest.
// Set policy to a custom access policy that further limits the permissions of the temporary STS access credentials. If you do not specify a policy, the returned temporary STS access credentials have all permissions of the specified role by default.
// 3600 is the expiration time, in seconds.
// Use sessionName to specify a custom role session name that distinguishes different tokens, for example, sessiontest.
sts.assumeRole('<YOUR-ROLE-ARN>', ``, '3600', '<YOUR-ROLE-SESSION-NAME>').then((result) => {
console.log(result);
// Return Expiration together with the credentials. The client uses it to refresh the credentials before they expire.
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
# 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>'
# Replace <YOUR-REGION> with the region ID of the STS service, for example, ap-southeast-1.
region_id = '<YOUR-REGION>'
def get_sts_token():
# If you do not specify parameters when you initialize CredentialClient, the default credential provider chain is used.
# When you run the program on your computer, you can use the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables to specify the AccessKey pair.
# When you run the program on ECS, ECI, or a container service, you can use the ALIBABA_CLOUD_ECS_METADATA environment variable to specify the attached instance RAM role. The SDK automatically obtains temporary STS 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)
# The serialized credentials contain the AccessKeyId, AccessKeySecret, SecurityToken, and Expiration fields.
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 by 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,
}
// Replace <YOUR-REGION> with the region ID of the STS service, for example, ap-southeast-1. For more information about endpoints, see 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),
// Replace <YOUR-ROLE-ARN> with the ARN of the RAM role that has permissions to upload files to the specified OSS bucket, for example, acs:ram::175708322470****:role/ramtest.
RoleArn: tea.String("<YOUR-ROLE-ARN>"),
// Set <YOUR-ROLE-SESSION-NAME> to a custom session name, for example, 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)
}
// Serialize only the credentials so that the client receives a flat JSON object.
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;
// Initialize the Alibaba Cloud client.
AlibabaCloud::accessKeyClient(getenv('ALIBABA_CLOUD_ACCESS_KEY_ID'), getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET'))
// Replace <YOUR-REGION> with the region ID of the STS service, for example, ap-southeast-1.
->regionId('<YOUR-REGION>')
->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 result of the STS request.
$credentials = $result->get('Credentials');
// Construct the flat JSON data to return. Expiration lets the client refresh the credentials before they expire.
$response = [
'AccessKeyId' => $credentials['AccessKeyId'],
'AccessKeySecret' => $credentials['AccessKeySecret'],
'SecurityToken' => $credentials['SecurityToken'],
'Expiration' => $credentials['Expiration'],
];
// 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($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,
// Replace <YOUR-REGION> with the region ID of the STS service, for example, 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);
// Register services before you build the application. The service collection is read-only after Build is called.
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();
// 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'],
# Replace <YOUR-REGION> with the ID of the region in which you call STS, for example, ap-southeast-1.
endpoint: 'https://sts.<YOUR-REGION>.aliyuncs.com',
api_version: '2015-04-01'
)
response = client.request(
action: 'AssumeRole',
params: {
# Replace <YOUR-ROLE-ARN> with the ARN of the RAM role that you created in the prerequisites, for example, acs:ram::175708322470****:role/ramtest.
"RoleArn": "<YOUR-ROLE-ARN>",
# 3600 is the expiration time, in seconds.
"DurationSeconds": 3600,
# Use RoleSessionName to specify a custom role session name that distinguishes different tokens, for example, 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()
# Return Expiration together with the credentials. The client uses it to refresh the credentials before they expire.
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
Exemple de code côté client
L'exemple de code suivant montre comment utiliser des identifiants d'accès temporaires pour charger un fichier vers OSS depuis un client web :
let credentials = null;
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
// To reduce the number of calls to the STS service, obtain the temporary credentials again only after 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 the OSS bucket.
bucket: "<YOUR-BUCKET>",
// Replace <YOUR-REGION> with the ID of the region in which the OSS bucket is located, for example, 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);
});
/**
* 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;
}La méthode put se résout une fois qu'OSS a stocké l'objet, et l'exemple consigne le résultat dans la console du navigateur. L'objet est stocké dans le compartiment que vous avez spécifié dans la configuration du client, sous le nom du fichier sélectionné par l'utilisateur. En cas d'échec du chargement, vérifiez les erreurs dans la console du navigateur et confirmez que les règles CORS du compartiment décrites dans les conditions préalables autorisent l'origine qui sert votre page.
Méthode 2 : Générer des signatures et des PostPolicies sur le serveur pour PostObject
Avec cette méthode, le navigateur soumet un formulaire HTML directement à OSS. Le client n'utilise pas le SDK Browser.js : l'exemple de code côté client s'appuie uniquement sur les API FormData et fetch du navigateur.
La figure suivante illustre le processus par lequel le serveur utilise une signature Post et une PostPolicy pour autoriser un client à charger des fichiers vers OSS.
Le processus comprend les étapes suivantes :
Le client demande au serveur d'application des informations telles que la signature Post et la PostPolicy.
Le serveur d'application génère ces informations (signature Post, PostPolicy, etc.) et les renvoie au client.
Le client utilise la signature Post, la PostPolicy et d'autres informations pour téléverser un fichier vers OSS en appelant l'opération PostObject depuis un formulaire HTML.
-
OSS renvoie une réponse de succès au client.
La PostPolicy générée par le serveur restreint les fichiers que le client peut téléverser. Par exemple, vous pouvez limiter la taille du fichier et le préfixe du nom de l'object.
Chaque condition définie dans la PostPolicy doit correspondre à un champ présent dans le formulaire soumis par le client. Si une condition n'est pas respectée, OSS rejette le téléversement.
Exemple de code
Les sections suivantes présentent les extraits de code principaux. Pour obtenir le code complet, consultez l'exemple de projet : postsignature.zip.
Exemple de code côté serveur
Chacun des extraits suivants renvoie les champs host, policy, signature, ossAccessKeyId et dir que l'exemple de code côté client ajoute au formulaire de téléversement. Ces extraits diffusent cette réponse sur le chemin /get_post_signature_for_oss_upload, qui est celui demandé par l'exemple de code côté client. Les extraits Python et PHP ne contiennent que la logique de génération de la signature ; intégrez-les donc à ce chemin dans le framework web utilisé par votre serveur.
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());
}
// Do not shut down the injected OSS client here. It is shared by all requests and is released when the application stops.
return response.toString();
}
}
Déclarez le client OSS et les paramètres de téléversement en tant que classe de configuration Spring dans un fichier distinct :
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 {
/**
* Replace <YOUR-ENDPOINT> with the endpoint of the region in which the bucket is located, for example, oss-ap-southeast-1.aliyuncs.com.
*/
private String endpoint = "<YOUR-ENDPOINT>";
/**
* Replace <YOUR-BUCKET> with the bucket name.
*/
private String bucket = "<YOUR-BUCKET>";
/**
* Specify the prefix of the files 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 = "https://" + bucket + "." + endpoint;
/**
* Set accessKeyId by using the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
*/
private String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
/**
* Set accessKeySecret by 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 files 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 = `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
# Configure the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
access_key_id = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_ID')
# Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
access_key_secret = os.environ.get('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
# Replace <YOUR-BUCKET> with the bucket name.
bucket = '<YOUR-BUCKET>'
# The host is in the bucketname.endpoint format. Replace <YOUR-BUCKET> with the bucket name. Replace <YOUR-ENDPOINT> with the OSS endpoint, for example, oss-ap-southeast-1.aliyuncs.com.
host = 'https://<YOUR-BUCKET>.<YOUR-ENDPOINT>'
# Specify the prefix of the files 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 that has permissions to access the destination bucket.
:param expiration: The expiration time of the signature. The value follows the ISO 8601 standard, must be in UTC, and is in the yyyy-MM-ddTHH:mm:ssZ format. Example: "2014-12-01T12:00:00.000Z".
:param conditions: The policy conditions that limit the values allowed in the upload form.
:param policy_extra_props: Additional policy parameters. If the policy supports new parameters later, you can pass the additional parameters as a dict.
: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 = {
# The validity period.
"expiration": generate_expiration(expire_time),
# The 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 the form field must start with the specified prefix. For example, to specify that the value of key must start with user/user1, set this condition 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", 0, 1048576000]
# Add a condition only if the client-side form submits a matching field. Otherwise, OSS rejects the upload.
]
}
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 append 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 bucketname.endpoint format. Replace <YOUR-BUCKET> with the bucket name. Replace <YOUR-ENDPOINT> with the OSS endpoint, for example, oss-ap-southeast-1.aliyuncs.com.
host = "https://<YOUR-BUCKET>.<YOUR-ENDPOINT>"
// Specify the prefix of the files 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 the 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 host is in the bucketname.endpoint format. Replace <YOUR-BUCKET> with the bucket name. Replace <YOUR-ENDPOINT> with the OSS endpoint, for example, oss-ap-southeast-1.aliyuncs.com.
$host = 'https://<YOUR-BUCKET>.<YOUR-ENDPOINT>';
// The prefix that the user specifies when uploading a file.
$dir = 'user-dir-prefix/';
$now = time();
// Specify the expiration time, in seconds. After the validity period elapses, the policy can no longer be used for access.
$expire = 3600;
$end = $now + $expire;
$expiration = gmt_iso8601($end);
//The maximum file size. You can set this value as needed.
$condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000);
$conditions[] = $condition;
// The data that the user uploads must start with $dir. Otherwise, the upload fails. This step is optional and is used for security purposes to prevent users from using the policy to upload files to other users' directories.
$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_KEY_ID']
# Configure the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
$access_key_secret = ENV['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
# The host is in the bucketname.endpoint format. Replace <YOUR-BUCKET> with the bucket name. Replace <YOUR-ENDPOINT> with the OSS endpoint, for example, oss-ap-southeast-1.aliyuncs.com.
$host = 'https://<YOUR-BUCKET>.<YOUR-ENDPOINT>'
# The prefix that the user specifies when uploading a file.
$upload_dir = 'user-dir-prefix/'
# Specify the expiration time, in seconds.
$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;
// 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 bucketname.endpoint format. Replace <YOUR-BUCKET> with the bucket name. Replace <YOUR-ENDPOINT> with the OSS endpoint, for example, oss-ap-southeast-1.aliyuncs.com.
public string Host { get; set; } = "https://<YOUR-BUCKET>.<YOUR-ENDPOINT>";
// Specify the prefix of the files 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);
// Register services before you build the application. The service collection is read-only after Build is called.
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
{
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)));
}
}
}
}
Exemple de code côté client
L'exemple de code suivant montre comment utiliser des informations telles qu'une signature Post et une PostPolicy pour téléverser un fichier vers OSS depuis un client web :
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);
});
});Méthode 3 : Générer des URL signées sur le serveur pour PutObject
Avec cette méthode, le navigateur envoie une requête PUT vers une URL signée par le serveur. Le client n'utilise pas le SDK Browser.js : l'exemple de code côté client s'appuie uniquement sur l'API fetch du navigateur.
Le schéma suivant illustre le processus par lequel le serveur autorise un client à téléverser des fichiers vers OSS à l'aide d'une URL signée.
Le processus comprend les étapes suivantes :
Le client demande une URL signée au serveur d'application.
Le serveur d'application utilise un SDK OSS pour générer une URL signée pour une requête PUT et renvoie cette URL au client.
Le client utilise l'URL signée pour la requête PUT afin de télécharger un fichier vers OSS en appelant l'opération PutObject.
-
OSS renvoie une réponse de succès au client.
Cette méthode ne convient pas aux téléchargements multiparties de fichiers volumineux ni aux reprises de téléchargement basées sur le mode multipartie. Si vous générez une URL signée pour chaque partie sur le serveur et que vous renvoyez ces URL au client, le nombre d'interactions avec le serveur augmente et les requêtes réseau deviennent plus complexes. De plus, le client risque de modifier le contenu ou l'ordre des parties, ce qui entraîne une erreur dans l'objet final fusionné.
Si vous souhaitez utiliser une URL signée contenant des paramètres facultatifs côté frontend, assurez-vous que la valeur Content-Type spécifiée lors de la génération de l'URL signée par le serveur est identique à celle indiquée lors de l'utilisation de l'URL côté frontend. Dans le cas contraire, une erreur SignatureDoesNotMatch peut se produire. Pour plus d'informations sur la définition du Content-Type, consultez Comment définir le Content-Type (MIME) ?.
Exemple de code
Les sections suivantes présentent les extraits de code principaux. Pour obtenir le code complet, consultez l'exemple de projet : presignedurl.zip.
Exemple de code côté serveur
Chacun des extraits suivants renvoie l'URL signée sous forme de texte brut à l'adresse /get_presigned_url_for_oss_upload, qui correspond au chemin demandé par l'exemple de code côté client. Les extraits signent le type de contenu image/png, qui correspond au Content-Type envoyé par l'exemple de code côté client. L'extrait Python contient uniquement la logique de génération d'URL ; vous devez donc le monter sur ce chemin dans le framework web utilisé par votre serveur. L'extrait Ruby nécessite une modification supplémentaire, décrite dans l'avertissement de l'onglet 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 {
/**
* Configure the OSS endpoint, for example, oss-ap-southeast-1.aliyuncs.com.
*/
private static final String endpoint = "https://oss-ap-southeast-1.aliyuncs.com";
/**
* Set accessKeyId by using the ALIBABA_CLOUD_ACCESS_KEY_ID environment variable.
*/
@Value("${ALIBABA_CLOUD_ACCESS_KEY_ID}")
private String accessKeyId;
/**
* Set accessKeySecret by using the ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variable.
*/
@Value("${ALIBABA_CLOUD_ACCESS_KEY_SECRET}")
private String accessKeySecret;
private OSS ossClient;
@Bean
public OSS getOssClient() {
// Create an OSSClient instance.
// When the OSSClient instance is no longer used, call the shutdown method to release resources.
ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
return ossClient;
}
@PreDestroy
public void onDestroy() {
ossClient.shutdown();
}
}
@Controller
public class PresignedURLController {
/**
* Replace <YOUR-BUCKET> with the bucket name.
* Replace <YOUR-OBJECT> with the full path of the object, for example, exampledir/exampleobject.png. The full path of the object 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 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: process.env.ALIBABA_CLOUD_ACCESS_KEY_ID,
accessKeySecret: process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET,
// Replace <YOUR-BUCKET> with the bucket name.
bucket: '<YOUR-BUCKET>',
// Replace <YOUR-REGION> with the region in which the bucket is located, for example, ap-southeast-1.
region: '<YOUR-REGION>',
authorizationV4: true,
});
// Sign the same Content-Type that the client sends. Otherwise, a SignatureDoesNotMatch error occurs.
const url = await client.signatureUrlV4('PUT', 3600, {
headers: {
'Content-Type': 'image/png',
},
}, 'exampledir/exampleobject.png');
// Return the signed URL as plain text. The client-side sample code reads the response body by calling 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
# Obtain the 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 in which the bucket is located, for example, https://oss-ap-southeast-1.aliyuncs.com.
endpoint = "https://oss-ap-southeast-1.aliyuncs.com"
# Specify the region that corresponds to the endpoint, for example, ap-southeast-1. This parameter is required for V4 signatures.
region = "ap-southeast-1"
# Replace <YOUR-BUCKET> with the bucket name.
bucket = oss2.Bucket(auth, endpoint, "<YOUR-BUCKET>", 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 of the object cannot contain the bucket name.
object_name = 'exampledir/exampleobject.png'
def generate_presigned_url():
# Specify the headers.
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 used directly.
# 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 used directly.
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 {
// Specify the endpoint of the region in which the bucket is located, for example, https://oss-ap-southeast-1.aliyuncs.com.
endpoint := "https://oss-ap-southeast-1.aliyuncs.com"
// Replace <YOUR-BUCKET> with the bucket name.
bucketName := "<YOUR-BUCKET>"
// Specify the full path of the file, for example, exampledir/exampleobject.png. The full path of the file cannot contain the bucket name.
objectName := "exampledir/exampleobject.png"
// Obtain the 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 {
log.Fatal("Failed to obtain the access credentials: ", err)
}
clientOptions := []oss.ClientOption{oss.SetCredentialsProvider(&provider)}
// Replace <YOUR-REGION> with the region in which the bucket is located, for example, ap-southeast-1.
clientOptions = append(clientOptions, oss.Region("<YOUR-REGION>"))
// Specify the signature version.
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"),
}
// Specify a validity period of 3,600s.
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
# 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 of the object 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>')
# Replace the following placeholder with a call that signs a PUT request for object_key on bucket and specifies a validity period. The maximum validity period is 32,400s.
'<YOUR-PUT-SIGNED-URL>'
end
client = Aliyun::OSS::Client.new(
# Replace <YOUR-ENDPOINT> with the endpoint of the region in which the bucket is located, for example, https://oss-ap-southeast-1.aliyuncs.com.
endpoint: '<YOUR-ENDPOINT>',
# Pass in the access credentials that are read from the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.
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
Cet extrait n'inclut pas l'appel permettant de générer une URL signée pour une requête PUT. Avant d'utiliser cet extrait, remplacez l'espace réservé <YOUR-PUT-SIGNED-URL> par un appel qui signe une requête PUT et définit la période de validité. Sinon, OSS rejettera la requête PUT du client avec une erreur SignatureDoesNotMatch ou 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;
// 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 in which the bucket is located, for example, https://oss-ap-southeast-1.aliyuncs.com.
private string EndPoint { get; set; } = "<YOUR-ENDPOINT>";
// Replace <YOUR-BUCKET> with the bucket name.
private string BucketName { get; set; } = "<YOUR-BUCKET>";
// Specify the full path of the object. The full path of the object cannot contain the bucket name.
private string ObjectName { get; set; } = "exampledir/exampleobject.png";
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add logging. Register services before you build the application. The service collection is read-only after Build is called.
builder.Logging.AddConsole();
var app = builder.Build();
var logger = app.Services.GetRequiredService<ILogger<Program>>();
// Enable the static file middleware.
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);
// 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 by using 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();
}
}
}
Exemple de code côté client
L'exemple de code suivant montre comment utiliser une URL signée pour télécharger un fichier vers OSS depuis un client web :
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);
});
});Prérequis
Le téléchargement direct depuis un navigateur dépend des ressources que vous créez avant d'exécuter l'un des exemples de code :
Règles CORS du bucket — La page qui héberge le formulaire de téléchargement et l'endpoint OSS ont des origines différentes ; le navigateur envoie donc une requête préliminaire avant le téléchargement. Configurez les règles CORS sur le bucket de destination pour autoriser l'origine qui sert votre page. Si CORS n'est pas configuré, le navigateur bloque le téléchargement avec une erreur
No 'Access-Control-Allow-Origin' headeravant que la requête n'atteigne OSS.Un rôle RAM pour STS — La méthode 1 appelle l'opération AssumeRole, qui nécessite un rôle RAM disposant des autorisations nécessaires pour télécharger des objets vers le bucket de destination. Obtenez l'ARN du rôle sur la page de détails du rôle RAM et utilisez-le partout où l'exemple de code contient
<YOUR-ROLE-ARN>.Identifiants d'accès côté serveur — Le serveur d'application appelle STS et OSS avec une paire AccessKey à long terme. Configurez les variables d'environnement
ALIBABA_CLOUD_ACCESS_KEY_IDetALIBABA_CLOUD_ACCESS_KEY_SECRETsur le serveur avant de le démarrer. Le SDK OSS pour Python lit plutôt la paire AccessKey à partir deOSS_ACCESS_KEY_IDetOSS_ACCESS_KEY_SECRET; les extraits concernés l'indiquent explicitement.Contrôle d'accès pour les endpoints d'identifiants — Les serveurs d'exemple exposent
/get_sts_token_for_oss_upload,/get_post_signature_for_oss_uploadet/get_presigned_url_for_oss_uploaden tant qu'endpoints GET sans aucune authentification. Toute personne atteignant l'un de ces endpoints obtient un accès en écriture à votre bucket. En production, placez ces endpoints derrière les vérifications de connexion et d'autorisation de votre application, et limitez les permissions des identifiants que vous émettez.Une page hébergeant le formulaire de téléchargement — Chaque exemple côté client attend un élément de formulaire et une entrée de fichier dont l'ID est
filesur la page. La méthode 1 attend également l'objet globalOSSfourni par le SDK Browser.js. Ces deux éléments sont inclus danstemplates/index.htmldans les exemples de projets auxquels les sections suivantes font référence.
Références
Pour obtenir l'exemple de code complet relatif à l'utilisation de STS pour accorder un accès temporaire, consultez l'exemple GitHub.
Pour obtenir l'exemple de code complet relatif à l'utilisation d'URL signées pour accorder un accès temporaire, consultez l'exemple GitHub.