当所用编程语言没有对应的SDK,或需要自行控制依赖组件时,可以直接调用OpenAPI获取Token。本文介绍请求参数、签名机制和响应格式,并提供Java和Python示例。
准备访问凭证
调用接口需要AccessKey ID和AccessKey Secret。将访问凭证配置到运行环境的 ALIYUN_AK_ID 和 ALIYUN_AK_SECRET 环境变量中,示例代码从环境变量读取凭证。
AccessKey Secret和Token均为敏感信息,不要写入公开代码或日志。以下示例会输出包含Token的响应,集成到应用时应妥善保管。
代码示例
以下示例通过HTTPS GET请求获取Token。响应中的 Token.Id 为Token值,Token.ExpireTime 为过期时间戳,单位为秒。
Java
使用JDK 8或更高版本,并添加以下Maven依赖。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.1</version>
</dependency>
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Map;
import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class CreateToken {
private static String encode(String value) throws Exception {
return URLEncoder.encode(value, "UTF-8")
.replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
}
public static void main(String[] args) throws Exception {
String accessKeyId = System.getenv("ALIYUN_AK_ID");
String accessKeySecret = System.getenv("ALIYUN_AK_SECRET");
if (accessKeyId == null || accessKeyId.isEmpty()
|| accessKeySecret == null || accessKeySecret.isEmpty()) {
throw new IllegalArgumentException("Set ALIYUN_AK_ID and ALIYUN_AK_SECRET.");
}
String endpoint = "https://nls-meta.cn-shanghai.aliyuncs.com/";
Map<String, String> parameters = new TreeMap<>();
parameters.put("AccessKeyId", accessKeyId);
parameters.put("Action", "CreateToken");
parameters.put("Version", "2019-02-28");
parameters.put("RegionId", "cn-shanghai");
parameters.put("Format", "JSON");
parameters.put("SignatureMethod", "HMAC-SHA1");
parameters.put("SignatureVersion", "1.0");
parameters.put("SignatureNonce", UUID.randomUUID().toString());
parameters.put("Timestamp", Instant.now().truncatedTo(ChronoUnit.SECONDS).toString());
StringJoiner query = new StringJoiner("&");
for (Map.Entry<String, String> entry : parameters.entrySet()) {
query.add(encode(entry.getKey()) + "=" + encode(entry.getValue()));
}
String canonicalQuery = query.toString();
String stringToSign = "GET&" + encode("/") + "&" + encode(canonicalQuery);
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec((accessKeySecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
String signature = Base64.getEncoder().encodeToString(
mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));
Request request = new Request.Builder()
.url(endpoint + "?" + canonicalQuery + "&Signature=" + encode(signature))
.get().build();
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
String body = response.body() == null ? "" : response.body().string();
if (!response.isSuccessful()) {
throw new IllegalStateException("HTTP " + response.code() + ": " + body);
}
System.out.println(body);
}
}
}
Python
使用Python 3.4或更高版本,安装Requests库。示例使用Python 3.14.7。
pip install requests
import base64
import hashlib
import hmac
import os
import uuid
from datetime import datetime, timezone
from urllib.parse import quote
import requests
def encode(value):
return quote(value, safe="-_.~")
def create_token():
access_key_id = os.environ["ALIYUN_AK_ID"]
access_key_secret = os.environ["ALIYUN_AK_SECRET"]
endpoint = "https://nls-meta.cn-shanghai.aliyuncs.com/"
parameters = {
"AccessKeyId": access_key_id,
"Action": "CreateToken",
"Version": "2019-02-28",
"RegionId": "cn-shanghai",
"Format": "JSON",
"SignatureMethod": "HMAC-SHA1",
"SignatureVersion": "1.0",
"SignatureNonce": str(uuid.uuid4()),
"Timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
}
canonical_query = "&".join(
encode(key) + "=" + encode(value)
for key, value in sorted(parameters.items())
)
string_to_sign = "GET&" + encode("/") + "&" + encode(canonical_query)
signature = base64.b64encode(hmac.new(
(access_key_secret + "&").encode("utf-8"),
string_to_sign.encode("utf-8"), hashlib.sha1
).digest()).decode("ascii")
response = requests.get(
endpoint + "?" + canonical_query + "&Signature=" + encode(signature),
timeout=20,
)
if response.status_code != 200:
raise RuntimeError("HTTP {}: {}".format(response.status_code, response.text))
return response.text
if __name__ == "__main__":
print(create_token())
请求说明
接口支持GET和POST方法,使用阿里云POP协议进行签名。HTTP和HTTPS请求的参数相同,建议使用HTTPS。
服务地址
Endpoint:https://nls-meta.cn-shanghai.aliyuncs.com/,RegionId:cn-shanghai,API版本:2019-02-28。
请求参数
|
名称 |
类型 |
必选 |
说明 |
|
AccessKeyId |
String |
是 |
AccessKey ID。 |
|
Action |
String |
是 |
接口名称,固定为 |
|
Version |
String |
是 |
API版本,固定为 |
|
Format |
String |
否 |
响应格式。设为 |
|
RegionId |
String |
是 |
地域ID,固定为 |
|
Timestamp |
String |
是 |
请求时间戳,使用UTC时间,格式为 |
|
SignatureMethod |
String |
是 |
签名算法,固定为 |
|
SignatureVersion |
String |
是 |
签名算法版本,固定为 |
|
SignatureNonce |
String |
是 |
唯一随机字符串,用于防止重放攻击,每次请求不得重复。可使用UUID,例如 |
|
Signature |
String |
是 |
根据除Signature外的全部请求参数计算得到的签名,计算方法见下文签名机制。 |
请求头与参数传递
GET请求将参数放在URL查询字符串中;POST请求将参数放在请求体中。
|
请求头 |
必选 |
说明 |
|
Host |
否 |
服务域名,通常由HTTP客户端根据Endpoint自动设置。 |
|
Accept |
否 |
可设置为 |
|
Content-Type |
POST必选 |
POST请求设置为 |
请求格式如下,<encoded-parameters> 表示URL编码后的参数字符串,包含Signature。POST请求计算签名时使用 POST,不能直接复用GET签名。
GET
GET /?<encoded-parameters> HTTP/1.1
Host: <endpoint-host>
Accept: application/json
POST
POST / HTTP/1.1
Host: <endpoint-host>
Accept: application/json
Content-Type: application/x-www-form-urlencoded
<encoded-parameters>
签名机制
每个请求都必须携带签名。客户端使用AccessKey Secret计算HMAC-SHA1消息认证码;服务端根据请求中的AccessKey ID查找对应密钥,并校验签名,以验证身份和请求完整性。
构造规范化请求字符串
排除Signature参数,按参数名进行区分大小写的字典排序。
对参数名和参数值分别进行UTF-8 URL编码:
A-Z、a-z、0-9、-、_、.、~不编码。其他字符按其UTF-8字节编码为
%XY,例如双引号编码为%22。空格编码为
%20,不能编码为加号+。
使用
=连接编码后的参数名和值,再用&连接各参数对。第一个参数前不加&。
percentEncode(name1)=percentEncode(value1)&percentEncode(name2)=percentEncode(value2)
Java的 URLEncoder 按表单规则编码,需将编码结果中的 + 替换为 %20、* 替换为 %2A、%7E 替换为 ~。
构造待签名字符串
将大写HTTP方法、编码后的请求路径 / 和再次编码的规范化请求字符串用 & 连接。
StringToSign = HTTPMethod + "&" + percentEncode("/") + "&" + percentEncode(CanonicalizedQueryString)
计算并添加签名
在AccessKey Secret末尾追加
&,作为HMAC-SHA1密钥。使用UTF-8编码的密钥和待签名字符串计算HMAC-SHA1,再对结果进行Base64编码。
对Base64结果进行URL编码,以
Signature=<encoded-signature>的形式加入请求参数。
Signature = Base64(HMAC-SHA1(AccessKeySecret + "&", StringToSign))
签名校验示例
以下固定参数用于离线核对签名实现。AccessKey为虚拟值,Timestamp已过期,不能用于实际获取Token。
AccessKeyId: my_access_key_id
AccessKeySecret: my_access_key_secret
Action: CreateToken
Format: JSON
SignatureMethod: HMAC-SHA1
SignatureVersion: 1.0
SignatureNonce: b924c8c3-6d03-4c5d-ad36-d984d3116788
Timestamp: 2019-04-18T08:32:31Z
HTTPMethod: GET
规范化请求字符串:
AccessKeyId=my_access_key_id&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=b924c8c3-6d03-4c5d-ad36-d984d3116788&SignatureVersion=1.0&Timestamp=2019-04-18T08%3A32%3A31Z&Version=2019-02-28
待签名字符串:
GET&%2F&AccessKeyId%3Dmy_access_key_id%26Action%3DCreateToken%26Format%3DJSON%26RegionId%3Dcn-shanghai%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3Db924c8c3-6d03-4c5d-ad36-d984d3116788%26SignatureVersion%3D1.0%26Timestamp%3D2019-04-18T08%253A32%253A31Z%26Version%3D2019-02-28
Base64签名:
hHq4yNsPitlfDJ2L0nQPdugdEzM=
URL编码后的签名参数:
Signature=hHq4yNsPitlfDJ2L0nQPdugdEzM%3D
响应说明
以下说明适用于 Format=JSON。GET和POST方法的响应格式相同。
成功响应
HTTP状态码为200,主要字段如下。
|
字段 |
类型 |
说明 |
|
Token |
Object |
包含Token值和过期时间。 |
|
Token.Id |
String |
Token值。 |
|
Token.ExpireTime |
Long |
Token的过期时间,以Unix时间戳表示,单位为秒;不是有效时长。例如1553825814对应UTC时间2019-03-29 02:16:54,Token在该时间之前有效。 |
响应中的Token字段示例如下。
{
"Token": {
"Id": "<token>",
"ExpireTime": 1553825814
}
}
失败响应
HTTP状态码为非200,根据Code和Message检查请求参数。
|
字段 |
类型 |
说明 |
|
RequestId |
String |
请求ID,可用于排查问题。 |
|
Code |
String |
错误码。 |
|
Message |
String |
错误说明。 |
例如,AccessKey ID不存在时,返回HTTP 404,响应中包含以下字段。
{
"RequestId": "<request-id>",
"Code": "InvalidAccessKeyId.NotFound",
"Message": "Specified access key is not found."
}
相关文档
临时调试时,可通过控制台获取Token。
使用SDK封装签名和请求逻辑时,请参见通过SDK获取Token。