V2 署名メカニズムを使用して、ROA スタイルの HTTP リクエストに署名し、Alibaba Cloud OpenAPI に送信します。
このアクセス方法は現在推奨されていません。代わりに V3 リクエストボディと署名メカニズム を使用してください。
HTTP リクエストの構造
完全な OpenAPI リクエストには、以下の部分が含まれます。
|
名前 |
必須 |
説明 |
例 |
|
プロトコル |
はい |
リクエストプロトコルです。サポートされているプロトコルについては、API メタデータ をご確認ください。利用可能な場合は HTTPS を使用してください。 |
https:// |
|
エンドポイント |
はい |
サービスのエンドポイントです。プロダクト固有のエンドポイントドキュメントをご確認ください。 |
bailian.cn-beijing.aliyuncs.com |
|
resource_URI_parameters |
はい |
パスとクエリパラメーターを含む API の URL です。 |
/{WorkspaceId}/datacenter/category |
|
RequestHeader |
はい |
API バージョン、Host、Authorization を含むリクエストヘッダーです。詳細は後述の RequestHeader で定義されています。 |
Accept:application/json Authorization:acs YourAccessKeyId:s8ZMF/eAIvvPJwehLLha0bVNFJ0= Content-MD5:LP54yxk8n7KqF1PPgbJizw== Content-Type:application/json Date:Wed, 16 Apr 2025 03:44:46 GMT Host:bailian.cn-beijing.aliyuncs.com x-acs-signature-method:HMAC-SHA1 x-acs-signature-nonce:ef34aae7-7bd2-413d-a541-680cd2c48538 x-acs-signature-version:1.0 x-acs-version:2023-12-29 |
|
RequestBody |
はい |
ビジネスパラメーターを含むリクエストボディです。リクエストのテストは Alibaba Cloud OpenAPI Developer Portal で行えます。 |
{"CategoryName":"test","CategoryType":"UNSTRUCTURED"} |
|
HTTPMethod |
はい |
HTTP メソッド (PUT、POST、GET、または DELETE) です。必要なメソッドは API ドキュメントで指定されています。 |
POST |
RequestHeader
RequestHeader には以下のフィールドが含まれます。
|
名前 |
タイプ |
必須 |
説明 |
例 |
|
Accept |
String |
いいえ |
応答フォーマット。ROA は |
application/json |
|
Content-MD5 |
String |
いいえ |
RequestBody の 128 ビット MD5 ハッシュを Base64 エンコードしたものです。 |
LP54yxk8n7KqF1PPgbJizw== |
|
Date |
String |
はい |
HTTP 1.1 の GMT フォーマットでの現在のタイムスタンプです。15 分間有効です。 |
Wed, 16 Apr 2025 03:44:46 GMT |
|
Host |
String |
はい |
サービスのエンドポイントです。HTTP リクエストの構造 のエンドポイントと一致する必要があります。 |
bailian.cn-beijing.aliyuncs.com |
|
x-acs-signature-method |
String |
匿名リクエスト以外では必須 |
署名アルゴリズム。固定値: |
HMAC-SHA1 |
|
x-acs-signature-nonce |
String |
はい |
リプレイ攻撃を防ぐためのユニークな乱数です。リクエストごとに異なる必要があります。 |
ef34aae7-7bd2-413d-a541-680cd2c48538 |
|
x-acs-signature-version |
String |
いいえ |
署名バージョン。固定値: |
1.0 |
|
x-acs-version |
String |
はい |
API バージョン。Alibaba Cloud OpenAPI Developer Portal または API メタデータ でバージョンを確認してください。 |
2023-12-29 |
|
Authorization |
String |
匿名リクエスト以外では必須 |
Signature は、署名メカニズム に従って計算されたリクエスト署名です。 |
acs YourAccessKeyId:D9uFJAJgLL+dryjBfQK+YeqGtoY= |
署名メカニズム
すべてのリクエストには、認証のための署名を含める必要があります。Alibaba Cloud API Gateway は、リクエストパラメーターとヘッダーから署名を再計算し、送信された署名と比較して ID とデータ整合性を検証します。署名は次のように計算します:
リクエストと応答には UTF-8 エンコーディングを使用します。
ステップ 1:正規化ヘッダーの構築
正規化ヘッダー (CanonicalizedHeaders) は、x-acs- をプレフィックスとする非標準の HTTP ヘッダーです。次のように構築します:
-
RequestHeader から
x-acs-ヘッダー名を小文字に変換し、辞書順にソートします。 -
ヘッダー名と値の先頭と末尾のスペースをトリミングします。
-
各ヘッダー (最後のヘッダーを含む) の後に
\nを追加し、すべてのヘッダーを連結して CanonicalizedHeaders を作成します。
例:
x-acs-signature-method:HMAC-SHA1
x-acs-signature-nonce:ef34aae7-7bd2-413d-a541-680cd2c48538
x-acs-signature-version:1.0
x-acs-version:2023-12-29
ステップ 2:正規化リソースの構築
正規化リソース (CanonicalizedResource) を次のように構築します:
-
クエリ文字列パラメーターを名前の辞書順にソートし、
&で結合します。 -
ソートされたクエリ文字列を
?を付けてリソースパスに追加します。リソースパスは、ホストとクエリ文字列の間の URL セグメントで、/で始まります。クエリ文字列が存在しない場合は、リソースパスのみを使用します。
例:
/llm-p2e4XXXXXXXXsvtn/datacenter/category
ステップ 3:署名対象文字列の構築
次の擬似コードを使用して、署名対象文字列 (stringToSign) を構築します:
String stringToSign =
HTTPMethod + "\n" +
Accept + "\n" +
ContentMD5 + "\n" +
ContentType + "\n" +
Date + "\n" +
CanonicalizedHeaders +
CanonicalizedResource
|
パラメーター |
説明 |
|
HTTPMethod |
大文字の HTTP メソッド (例:POST、GET)。 |
|
Accept |
Accept ヘッダーの値。存在しない場合は空文字列。 |
|
ContentMD5 |
Content-MD5 ヘッダーの値。存在しない場合は空文字列。 |
|
ContentType |
Content-Type ヘッダーの値。存在しない場合は空文字列。 説明
RequestBody の MIME タイプ。 |
|
Date |
Date ヘッダーの値。 |
|
CanonicalizedHeaders |
ステップ 1:正規化ヘッダーの構築 で作成した正規化ヘッダー。 |
|
CanonicalizedResource |
ステップ 2:正規化リソースの構築 で作成した正規化リソース。 |
例:
POST
application/json
LP54yxk8n7KqF1PPgbJizw==
application/json
Wed, 16 Apr 2025 03:44:46 GMT
x-acs-signature-method:HMAC-SHA1
x-acs-signature-nonce:ef34aae7-7bd2-413d-a541-680cd2c48538
x-acs-signature-version:1.0
x-acs-version:2023-12-29
/llm-p2e4XXXXXXXXsvtn/datacenter/category
ステップ 4:署名の計算
RFC2104 に従い、ステップ 3 の文字列を HMAC-SHA1 で署名し、結果を Base64 エンコードします。
String signature = Base64(HMAC_SHA1(SigningKey, stringToSign))
SigningKey はご利用の AccessKey Secret です。AccessKey を作成してください。
例:
s8ZMF/eAIvvPJwehLLha0bVNFJ0=
ステップ 5:Authorization ヘッダーの構築
Authorization ヘッダーを次の値に設定します。
String Authorization = "acs " + AccessKeyId + ":" + signature
値の例:
acs YourAccessKeyId:s8ZMF/eAIvvPJwehLLha0bVNFJ0=
ステップ 6:リクエストへの署名の追加とリクエストの送信
POST https://bailian.cn-beijing.aliyuncs.com/llm-p2e4XXXXXXXXsvtn/datacenter/category HTTP/1.1
Accept:application/json
Authorization:acs YourAccessKeyId:r8Y9ZqVhTrYGl4nieqk7CW0Pwow=
Content-MD5:LP54yxk8n7KqF1PPgbJizw==
Content-Type:application/json
Date:Wed, 16 Apr 2025 06:47:10 GMT
Host:bailian.cn-beijing.aliyuncs.com
x-acs-signature-method:HMAC-SHA1
x-acs-signature-nonce:e3d8efa7-b1d8-42f3-9733-4fe2691e15dc
x-acs-signature-version:1.0
x-acs-version:2023-12-29
{"CategoryName":"test","CategoryType":"UNSTRUCTURED"}
署名サンプルコード
Java の例
JDK 1.8 で動作します。ご利用の環境に合わせて調整してください。
pom.xml に以下の Maven 依存関係を追加します。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
public class SignatureDemo {
public static class SignatureRequest {
private final String httpMethod;
private final String host;
private final String version;
private final String canonicalUri;
public TreeMap<String, String> headers = new TreeMap<>();
public TreeMap<String, Object> queryParams = new TreeMap<>();
public byte[] bodyByteArray;
public SignatureRequest(String httpMethod, String host, String version, String canonicalUri) {
this.httpMethod = httpMethod;
this.host = host;
this.version = version;
this.canonicalUri = canonicalUri;
initHeaders();
}
private void initHeaders() {
headers.put("Host", host);
headers.put("x-acs-version", version);
headers.put("x-acs-signature-version", "1.0");
headers.put("Accept", "application/json");
headers.put("x-acs-signature-nonce", java.util.UUID.randomUUID().toString());
headers.put("Date", getGmtDate());
headers.put("x-acs-signature-method", "HMAC-SHA1");
}
private String getGmtDate() {
SimpleDateFormat gmtFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH);
gmtFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return gmtFormat.format(new Date());
}
public void setBody(Map<String, String> body, ContentType contentType) {
Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
this.bodyByteArray = gson.toJson(body).getBytes(StandardCharsets.UTF_8);
headers.put("Content-MD5", md5Sum(this.bodyByteArray));
headers.put("Content-Type", contentType.getMimeType());
}
}
private static final String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
private static final String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
private static final String ALGORITHM_NAME = "HmacSHA1";
public static void main(String[] args) {
// 例 1:POST リクエスト
String method = "POST";
String host = "bailian.cn-beijing.aliyuncs.com";
String version = "2023-12-29";
String canonicalUri = "/llm-p2e4XXXXXXXXsvtn/datacenter/category";
SignatureRequest signatureRequest = new SignatureRequest(method, host, version, canonicalUri);
Map<String, String> body = new HashMap<>();
body.put("CategoryName", "test");
body.put("CategoryType", "UNSTRUCTURED");
System.out.println(new Gson().toJson(body));
signatureRequest.setBody(body, ContentType.APPLICATION_JSON);
/*// 例 2:GET リクエスト
String method = "GET";
String host = "bailian.cn-beijing.aliyuncs.com";
String version = "2023-12-29";
String canonicalUri = "/llm-p2e4XXXXXXXXsvtn/datacenter/files";
SignatureRequest signatureRequest = new SignatureRequest(method, host, version, canonicalUri);
signatureRequest.queryParams.put("CategoryId", "cate_a946*********************_10045991");*/
/*// 例 3:DELETE リクエスト
String method = "DELETE";
String host = "bailian.cn-beijing.aliyuncs.com";
String version = "2023-12-29";
String canonicalUri = "/llm-p2e4XXXXXXXXsvtn/datacenter/category/cate_a946*********************_10045991";
SignatureRequest signatureRequest = new SignatureRequest(method, host, version, canonicalUri);*/
// Authorization の生成
generateSignature(signatureRequest);
// HTTPClient を使用してリクエストを送信
callApi(signatureRequest);
}
private static void generateSignature(SignatureRequest signatureRequest) {
try {
// ステップ 1:正規化ヘッダーの構築
String canonicalHeaders = buildCanonicalHeaders(signatureRequest);
// ステップ 2:正規化リソースの構築
String canonicalQueryString = buildQueryString(signatureRequest);
// ステップ 3:署名対象文字列の構築
String stringToSign = buildStringToSign(signatureRequest, canonicalQueryString, canonicalHeaders);
// ステップ 4:署名文字列の構築
String signature = signString(stringToSign);
// ステップ 5:Authorization ヘッダーの構築
String authorization = "acs " + ACCESS_KEY_ID + ":" + signature;
signatureRequest.headers.put("Authorization", authorization);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.toString());
}
}
private static void callApi(SignatureRequest signatureRequest) {
try {
// HttpClient を使用してリクエストを送信
String url = "https://" + signatureRequest.host + signatureRequest.canonicalUri;
URIBuilder uriBuilder = new URIBuilder(url);
// リクエストパラメーターの追加
signatureRequest.queryParams.forEach((key, value) -> uriBuilder.addParameter(key, String.valueOf(value)));
HttpUriRequest httpRequest;
switch (signatureRequest.httpMethod) {
case "GET":
httpRequest = new HttpGet(uriBuilder.build());
break;
case "POST":
HttpPost httpPost = new HttpPost(uriBuilder.build());
if (signatureRequest.bodyByteArray != null) {
httpPost.setEntity(new ByteArrayEntity(signatureRequest.bodyByteArray, ContentType.create(signatureRequest.headers.get("Content-Type"))));
}
httpRequest = httpPost;
break;
case "PUT":
HttpPut httpPut = new HttpPut(uriBuilder.build());
if (signatureRequest.bodyByteArray != null) {
httpPut.setEntity(new ByteArrayEntity(signatureRequest.bodyByteArray, ContentType.create(signatureRequest.headers.get("Content-Type"))));
}
httpRequest = httpPut;
break;
case "DELETE":
httpRequest = new HttpDelete(uriBuilder.build());
break;
default:
System.out.println("Unsupported HTTP method: " + signatureRequest.httpMethod);
throw new IllegalArgumentException("Unsupported HTTP method");
}
// HTTP リクエストヘッダーの追加
signatureRequest.headers.forEach(httpRequest::addHeader);
// リクエストの送信
try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
} catch (IOException e) {
// 例外処理
System.out.println("Failed to send request");
throw new IllegalArgumentException(e.toString());
}
} catch (URISyntaxException e) {
// 例外処理
System.out.println("Invalid URI syntax");
throw new IllegalArgumentException(e.toString());
}
}
private static String buildCanonicalHeaders(SignatureRequest signatureRequest) {
StringBuilder canonicalHeaders = new StringBuilder();
signatureRequest.headers.entrySet().stream()
.filter(entry -> entry.getKey().startsWith("x-acs-"))
.forEach(entry -> canonicalHeaders.append(entry.getKey().toLowerCase().trim()).append(":").append(entry.getValue().trim()).append("\n"));
return canonicalHeaders.toString();
}
private static String buildQueryString(SignatureRequest signatureRequest) {
StringBuilder queryBuilder = new StringBuilder(signatureRequest.canonicalUri);
if (!signatureRequest.queryParams.isEmpty()) {
queryBuilder.append("?");
}
for (Map.Entry<String, Object> entry : signatureRequest.queryParams.entrySet()) {
queryBuilder.append(entry.getKey());
String value = (String) entry.getValue();
if (value != null && !value.isEmpty()) {
queryBuilder.append("=").append(value).append("&");
} else {
queryBuilder.append("&");
}
}
String queryString = queryBuilder.toString();
if (queryString.endsWith("&")) {
queryString = queryString.substring(0, queryString.length() - 1);
}
return queryString;
}
private static String buildStringToSign(SignatureRequest signatureRequest, String canonicalQueryString, String canonicalHeaders) {
StringBuilder sb = new StringBuilder();
sb.append(signatureRequest.httpMethod).append("\n");
appendIfPresent(sb, signatureRequest.headers.get("Accept"));
appendIfPresent(sb, signatureRequest.headers.get("Content-MD5"));
appendIfPresent(sb, signatureRequest.headers.get("Content-Type"));
appendIfPresent(sb, signatureRequest.headers.get("Date"));
sb.append(canonicalHeaders);
sb.append(canonicalQueryString);
return sb.toString();
}
private static void appendIfPresent(StringBuilder sb, String value) {
if (value != null) {
sb.append(value).append("\n");
} else {
sb.append("\n");
}
}
private static String signString(String stringToSign) {
try {
Mac mac = Mac.getInstance(ALGORITHM_NAME);
mac.init(new SecretKeySpec(ACCESS_KEY_SECRET.getBytes(StandardCharsets.UTF_8), ALGORITHM_NAME));
byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printBase64Binary(signData);
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new IllegalArgumentException(ex.toString());
}
}
public static String md5Sum(byte[] buff) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] md5Bytes = md.digest(buff);
return DatatypeConverter.printBase64Binary(md5Bytes);
} catch (NoSuchAlgorithmException ex) {
throw new IllegalArgumentException(ex.toString());
}
}
}