OSS 相容 AWS S3 API,使用 AWS SDK 訪問 OSS 時,無需改動代碼,只需配置 OSS 的 Endpoint 和訪問憑證。
Endpoint:使用S3相容格式的外網Endpoint(
https://s3.oss-{region}.aliyuncs.com)或內網Endpoint(https://s3.oss-{region}-internal.aliyuncs.com)。將{region}替換為實際地區ID,如cn-hangzhou。完整地區列表見地區和Endpoint。訪問憑證:在RAM 存取控制建立有 OSS 存取權限的 AccessKey。
Java
SDK 2.x
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import java.net.URI;
S3Client s3Client = S3Client.builder()
.endpointOverride(URI.create("https://s3.oss-cn-hangzhou.aliyuncs.com"))
.region(Region.AWS_GLOBAL)
.serviceConfiguration(
S3Configuration.builder()
.pathStyleAccessEnabled(false)
.chunkedEncodingEnabled(false)
.build()
)
.build();SDK 1.x
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
.withEndpointConfiguration(new EndpointConfiguration(
"https://s3.oss-cn-hangzhou.aliyuncs.com",
"cn-hangzhou"))
.withPathStyleAccessEnabled(false)
.withChunkedEncodingDisabled(false)
.build();SDK 1.x 的 getObject 返回的 S3ObjectInputStream 調用 close() 會立即丟棄未讀資料,應先完整讀取。
S3Object object = s3Client.getObject("my-bucket", "file.txt");
InputStream input = object.getObjectContent();
byte[ ] data = IOUtils.toByteArray(input);
input.close();Python
import boto3
from botocore.config import Config
s3 = boto3.client(
's3',
endpoint_url='https://s3.oss-cn-hangzhou.aliyuncs.com',
config=Config(
signature_version='s3',
s3={'addressing_style': 'virtual'}
)
)
Node.js
SDK v3
import { S3Client } from '@aws-sdk/client-s3';
const client = new S3Client({
endpoint: 'https://s3.oss-cn-hangzhou.aliyuncs.com',
region: 'cn-hangzhou'
});SDK v2
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
endpoint: 'https://s3.oss-cn-hangzhou.aliyuncs.com',
region: 'cn-hangzhou'
});Go
SDK v2
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
cfg, _ := awsconfig.LoadDefaultConfig(context.TODO(),
awsconfig.WithEndpointResolverWithOptions(
aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: "https://s3.oss-cn-hangzhou.aliyuncs.com",
}, nil
}),
),
)
client := s3.NewFromConfig(cfg)SDK v1
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
sess := session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Endpoint: aws.String("https://s3.oss-cn-hangzhou.aliyuncs.com"),
Region: aws.String("cn-hangzhou"),
},
SharedConfigState: session.SharedConfigEnable,
}))
svc := s3.New(sess).NET
SDK 3.x
using Amazon.S3;
var config = new AmazonS3Config
{
ServiceURL = "https://s3.oss-cn-hangzhou.aliyuncs.com"
};
var client = new AmazonS3Client(config);SDK 2.x
using Amazon.S3;
var config = new AmazonS3Config
{
ServiceURL = "https://s3.oss-cn-hangzhou.aliyuncs.com"
};
var client = new AmazonS3Client(config);PHP
SDK 3.x
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Aws\S3\S3Client;
$s3Client = new S3Client([
'version' => '2006-03-01',
'region' => 'cn-hangzhou',
'endpoint' => 'https://s3.oss-cn-hangzhou.aliyuncs.com'
]);SDK 2.x
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Aws\S3\S3Client;
$s3Client = S3Client::factory([
'version' => '2006-03-01',
'region' => 'cn-hangzhou',
'base_url' => 'https://s3.oss-cn-hangzhou.aliyuncs.com'
]);Ruby
SDK 3.x
require 'aws-sdk-s3'
s3 = Aws::S3::Client.new(
endpoint: 'https://s3.oss-cn-hangzhou.aliyuncs.com',
region: 'cn-hangzhou'
)SDK 2.x
require 'aws-sdk'
s3 = AWS::S3::Client.new(
s3_endpoint: 's3.oss-cn-hangzhou.aliyuncs.com',
region: 'cn-hangzhou',
s3_force_path_style: false
)
C++
要求SDK 1.7.68及以上版本。
#include <aws/s3/S3Client.h>
#include <aws/core/client/ClientConfiguration.h>
Aws::Client::ClientConfiguration config;
config.endpointOverride = "s3.oss-cn-hangzhou.aliyuncs.com";
config.region = "cn-hangzhou";
Aws::S3::S3Client s3_client(config);Browser
Web前端應用使用STS臨時憑證,禁止在用戶端寫入程式碼永久AccessKey。服務端調用AssumeRole擷取臨時憑證並返回給用戶端。完整教程見使用STS臨時訪問憑證訪問OSS。
import { S3Client } from '@aws-sdk/client-s3';
// 從服務端擷取STS臨時憑證
async function getSTSCredentials() {
const response = await fetch('https://your-server.com/api/sts-token');
return await response.json();
}
// 使用臨時憑證初始化S3用戶端
const client = new S3Client({
region: 'cn-hangzhou',
endpoint: 'https://s3.oss-cn-hangzhou.aliyuncs.com',
credentials: async () => {
const creds = await getSTSCredentials();
return {
accessKeyId: creds.accessKeyId,
secretAccessKey: creds.secretAccessKey,
sessionToken: creds.securityToken,
expiration: new Date(creds.expiration)
};
}
});Android
行動裝置 App(Android)使用STS臨時憑證,禁止在用戶端寫入程式碼永久AccessKey。服務端調用AssumeRole擷取臨時憑證並返回給用戶端。完整教程見使用STS臨時訪問憑證訪問OSS。
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
// 實現憑證提供器,從服務端擷取STS臨時憑證
public class OSSCredentialsProvider implements AWSCredentialsProvider {
@Override
public AWSCredentials getCredentials() {
// 從您的服務端擷取STS臨時憑證
// 請求 https://your-server.com/api/sts-token
String accessKeyId = fetchFromServer("accessKeyId");
String secretKeyId = fetchFromServer("secretKeyId");
String securityToken = fetchFromServer("securityToken");
return new BasicSessionCredentials(accessKeyId, secretKeyId, securityToken);
}
@Override
public void refresh() {
// 重新整理憑證
}
}
// 建立S3用戶端
AmazonS3 s3Client = AmazonS3Client.builder()
.withCredentials(new OSSCredentialsProvider())
.withEndpointConfiguration(new EndpointConfiguration(
"https://s3.oss-cn-hangzhou.aliyuncs.com", ""))
.build();
// 業務代碼
s3Client.putObject("my-bucket", "test.txt", "Hello OSS");iOS
行動裝置 App(iOS)使用STS臨時憑證,禁止在用戶端寫入程式碼永久AccessKey。服務端調用AssumeRole擷取臨時憑證並返回給用戶端。完整教程見使用STS臨時訪問憑證訪問OSS。
#import <AWSS3/AWSS3.h>
// 實現憑證提供器
@interface OSSCredentialsProvider : NSObject <AWSCredentialsProvider>
@end
@implementation OSSCredentialsProvider
- (AWSTask<AWSCredentials *> *)credentials {
return [[AWSTask taskWithResult:nil] continueWithBlock:^id(AWSTask *task) {
// 從服務端擷取STS臨時憑證
NSString *accessKey = [self fetchFromServer:@"accessKeyId"];
NSString *secretKey = [self fetchFromServer:@"secretKeyId"];
NSString *sessionToken = [self fetchFromServer:@"securityToken"];
AWSCredentials *credentials = [[AWSCredentials alloc]
initWithAccessKey:accessKey
secretKey:secretKey
sessionKey:sessionToken
expiration:[NSDate dateWithTimeIntervalSinceNow:3600]];
return [AWSTask taskWithResult:credentials];
}];
}
@end
// 配置S3用戶端
AWSEndpoint *endpoint = [[AWSEndpoint alloc] initWithURLString:@"https://s3.oss-cn-hangzhou.aliyuncs.com"];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc]
initWithRegion:AWSRegionUnknown
endpoint:endpoint
credentialsProvider:[[OSSCredentialsProvider alloc] init]];
[AWSS3 registerS3WithConfiguration:configuration forKey:@"OSS"];
AWSS3 *s3 = [AWSS3 S3ForKey:@"OSS"];
// 業務代碼
AWSS3PutObjectRequest *request = [AWSS3PutObjectRequest new];
request.bucket = @"my-bucket";
request.key = @"test.txt";
request.body = [@"Hello OSS" dataUsingEncoding:NSUTF8StringEncoding];
[[s3 putObject:request] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(@"Error: %@", task.error);
} else {
NSLog(@"Success");
}
return nil;
}];常見問題
上傳失敗:InvalidArgument: aws-chunked encoding is not supported
癥狀:上傳檔案時報錯:
InvalidArgument: aws-chunked encoding is not supported with the specified x-amz-content-sha256 value根本原因:
這是使用AWS SDK訪問OSS最常見的問題。OSS支援AWS Signature V4簽名演算法,但在傳輸編碼上有差異:
AWS S3:預設使用chunked encoding傳輸大檔案
OSS:不支援chunked encoding傳輸
原因分析:
部分SDK的V4簽名實現綁定了chunked encoding:
Python (boto3):V4簽名強制使用chunked encoding,無法禁用 → 改用V2簽名
Java:可以通過配置禁用chunked encoding
Go/Node.js:預設不使用chunked encoding,無需特殊處理
解決方案(按SDK分類):
SDK | 解決方案 | 原因 |
Python (boto3) | 使用V2簽名: | boto3的V4實現綁定chunked encoding,無法禁用 |
Java 1.x | V4簽名 + | 可以禁用chunked encoding |
Java 2.x | V4簽名 + | 可以禁用chunked encoding |
Go v1 | V4簽名 | 預設不使用chunked encoding |
Go v2 | V4簽名;Manager上傳大檔案需注意 | Manager功能可能使用chunked encoding |
Node.js v3 | V4簽名 | 預設不使用chunked encoding |
Python樣本(修複前後):
# 錯誤配置(boto3的V4實現使用chunked encoding)
s3 = boto3.client('s3',
endpoint_url='https://oss-cn-hongkong.aliyuncs.com',
config=Config(signature_version='v4'))
# 正確配置(boto3使用V2簽名)
s3 = boto3.client('s3',
endpoint_url='https://oss-cn-hongkong.aliyuncs.com',
config=Config(signature_version='s3')) # V2簽名是boto3的穩定方案技術說明:
OSS的V4簽名遵循AWS Signature Version 4規範,但要求:
要求標頭包含:
x-oss-content-sha256: UNSIGNED-PAYLOAD不使用
Transfer-Encoding: chunked傳輸方式
大部分SDK可以通過配置實現相容,但boto3的V4簽名實現與chunked encoding強耦合,因此boto3需使用V2簽名。
SDK版本和簽名版本選擇
版本選擇參考:
語言 | SDK版本 | 簽名版本 | 配置要點 |
Python | boto3最新版 | V2簽名( | boto3的V4實現與OSS不相容 |
Java 1.x | 最新1.x | V4簽名 | 需禁用chunked encoding |
Java 2.x | 最新2.x | V4簽名 | 需禁用chunked encoding |
Node.js | v3 | V4簽名(預設) | - |
Go v1 | 最新v1 | V4簽名(預設) | - |
Go v2 | 最新v2 | V4簽名(預設) | Manager上傳大檔案需注意 |
簽名版本說明:
OSS V4簽名:OSS完整支援AWS Signature V4演算法
V2簽名:boto3特殊情況,因SDK實現限制需使用V2
相容性:除boto3外,其他SDK均可使用V4簽名訪問OSS
新專案版本選擇參考:
情境 | 可選方案 | 原因 |
Python新專案 | boto3 + V2簽名 | boto3暫不支援OSS V4 |
Java新專案 | Java 2.x + V4簽名 | 效能更好 |
Node.js新專案 | v3 + V4簽名 | - |
Go新專案 | Go v1 + V4簽名 | 推薦 |
已有專案遷移 | 保持當前SDK版本 | 最小化改動風險 |
簽名錯誤:SignatureDoesNotMatch
可能遇到SignatureDoesNotMatch錯誤,提示服務端計算的簽名與用戶端提供的簽名不匹配。
最常見的原因是代碼中仍在使用AWS的AccessKey而不是OSS的AccessKey。AWS的訪問憑證和OSS的訪問憑證是完全獨立的兩套系統,不能混用。檢查代碼中的aws_access_key_id、aws_secret_access_key等參數,確保使用的是OSS控制台中建立的AccessKey ID和AccessKey Secret。
第二個常見原因是伺服器時鐘偏差。S3簽名演算法會在簽名中包含時間戳記,OSS服務端會驗證請求時間與伺服器時間的差異。如果你的伺服器時間與標準時間相差超過15分鐘,所有請求都會被拒絕。可以通過date -u命令檢查伺服器的UTC時間,如果時間不準確,使用ntpdate或系統時間同步服務校正時間。
第三個原因是endpoint配置錯誤。如果endpoint仍然指向AWS網域名稱(如s3.amazonaws.com),或者使用了錯誤的OSS region,簽名計算會失敗。OSS endpoint的標準格式是https://oss-{region}.aliyuncs.com,其中region與bucket所在的OSS地區一致,比如oss-cn-hangzhou、oss-cn-beijing等。
使用boto3時,還有一個特殊原因:如果未配置signature_version='s3',boto3會使用預設的V4簽名,這會導致簽名失敗。正確的boto3配置包含Config(signature_version='s3')參數。
驗證配置是否正確的一個簡單方法是使用ossutil命令列工具。運行ossutil ls oss://your-bucket --access-key-id <key> --access-key-secret <secret> --endpoint oss-cn-hangzhou.aliyuncs.com,如果能成功列出bucket內容,說明訪問憑證和endpoint配置是正確的,問題出在代碼配置上。
Bucket訪問錯誤
NoSuchBucket或AccessDenied錯誤表示無法訪問指定的bucket。最常見的原因是endpoint與bucket所在region不匹配。
OSS的每個bucket都屬於特定的region,比如cn-hangzhou、cn-beijing等。訪問bucket時,endpoint使用bucket所在region的網域名稱。如果您的bucket在杭州region,endpoint是oss-cn-hangzhou.aliyuncs.com,而不能使用北京region的oss-cn-beijing.aliyuncs.com。這與AWS S3不同,AWS S3允許跨region訪問,但會自動重新導向。OSS不支援跨region訪問,錯誤的endpoint會直接返回NoSuchBucket錯誤。
第二個原因是RAM許可權配置問題。檢查您的OSS AccessKey對應的RAM使用者是否有訪問目標bucket的許可權。在OSS控制台的Resource Access Management頁面,確認該帳號被授予了oss:ListObjects、oss:GetObject、oss:PutObject等必需的許可權。
第三個原因與bucket命名規範有關。OSS支援兩種URL樣式:虛擬機器主機樣式(bucket-name.oss-cn-hangzhou.aliyuncs.com)和路徑樣式(oss-cn-hangzhou.aliyuncs.com/bucket-name)。當使用虛擬機器主機樣式時,bucket名稱符合DNS命名規範,不能包含底線。如果您的bucket名稱包含底線,需要在SDK配置中使用路徑樣式,或者建立新的符合命名規範的bucket。
效能最佳化
大檔案上傳和下載是Object Storage Service應用中的常見需求。AWS SDK提供了多種傳輸加速機制,這些機制在OSS上同樣有效。
使用Python boto3時,可以通過TransferConfig配置分區上傳參數。當上傳檔案大於配置的閾值時,boto3會自動將檔案分成多個部分並發上傳,顯著提高傳輸速度。multipart_threshold參數控制啟用分區上傳的檔案大小閾值,max_concurrency控制並發上傳的線程數,multipart_chunksize控制每個分區的大小。合理配置這些參數可以讓100MB以上的大檔案上傳速度提升數倍。
使用Java SDK時,TransferManager類封裝了分區上傳、並發傳輸、自動重試等功能。TransferManager會根據檔案大小自動選擇最優的傳輸策略,無需手動處理分區邏輯。
使用Go SDK時,應該調用s3manager.Uploader而不是直接調用PutObject。Uploader內建了並發分區上傳功能,可以自動將大檔案分割並發上傳,同時處理上傳失敗的重試邏輯。
使用Node.js SDK時,可以使用@aws-sdk/lib-storage包提供的Upload類。這個類支援流式上傳,可以在讀取檔案的同時開始上傳,減少記憶體佔用。前面Node.js章節已經展示了使用樣本。
所有這些傳輸加速機制都是基於S3的分區上傳API(Multipart Upload),OSS完全相容這些API,因此可以直接使用,無需修改代碼。