使用服务账号调用火山引擎OpenAPI,获取SAMI音频技术API的服务鉴权Token。
open.volcengineapi.com
HTTP请求Content-Type: application/json
字段 | 描述 | 类型 | 是否必传 | 默认值 |
---|---|---|---|---|
Service | 音频技术对应填sami | string | 是 | - |
Region | 访问地区,填cn-north-1 | string | 是 | - |
access_key | 获取方式:用户指南-获取访问密钥 | string | 是 | - |
secret_key | 获取方式:用户指南-获取访问密钥 | string | 是 | - |
token_version | 填 volc-auth-v1 | string | 是 | - |
appkey | 服务接入appkey。在音频技术控制台创建应用后获得 | string | 是 | - |
expiration | token 的过期时间,单位是秒,可以自行定义,但不能超过 1 天(如果超过会被截断为1天)。建议在有效期内使用同一 token,避免重复申请。 | number | 是 | - |
HTTP响应Content-Type: application/json
字段 | 描述 | 类型 |
---|---|---|
task_id | 请求任务id,用于链路追踪、问题排查 | string |
token | 鉴权token,用于调用服务接口 | string |
expires_at | 过期时间戳 | number |
status_code | 状态码 | number |
status_text | 状态信息 | string |
示例:
{ "status_code": 20000000, "status_text": "OK", "task_id": "00000000-0000-0000-0000-000000000000", "token": "eyJhb...Ng", "expires_at": 1626796800 }
下面列出了几种编程语言实现的获取Token的参考示例。如果未覆盖到您所期待的编程语言,可以参考 API签名调用指南 来自行实现,如果遇到任何问题请联系技术支持。
package main import ( "encoding/json" "fmt" "net/http" "net/url" "time" "github.com/volcengine/volc-sdk-golang/base" ) const ( // user and app info accessKey = "your_access_key" secretKey = "your_secret_key" appKey = "your_appkey" ) // volcengine sdk including auth: https://github.com/volcengine // https://github.com/volcengine/volc-sdk-golang/blob/main/service/visual/README.md // when success: // {"code":0,"msg":"ok","token":"your_token_with_expiration"} func main() { DefaultInstance.Client.ServiceInfo.Credentials = base.Credentials{Region: "cn-north-1", Service: "sami"} DefaultInstance.Client.SetAccessKey(accessKey) DefaultInstance.Client.SetSecretKey(secretKey) // Token parameters tokenVersion := "volc-auth-v1" // volc-auth-v1 volc-offline-auth-v1 expiration := int64(3600) // expiration in seconds resp := &GetTokenResponse{} // Construct HTTP request to get SAMI token // support: query params, application/json, and application/x-www-form-urlencoded // 1. Construct HTTP request with query params // form := url.Values{} // form.Add("appkey", appKey) // form.Add("token_version", tokenVersion) // form.Add("expiration", strconv.FormatInt(expiration, 10)) // status, err := DefaultInstance.commonHandler("GetToken", form, resp) // 2. Construct HTTP request with json body jsonBody := fmt.Sprintf(`{"appkey":"%v","token_version":"%v","expiration":%v}`, appKey, tokenVersion, expiration) status, err := DefaultInstance.commonHandlerJSON("GetToken", jsonBody, &resp) fmt.Println(status, err) b, _ := json.Marshal(resp) fmt.Println(string(b)) } type GetTokenResponse struct { StatusCode int32 `form:"status_code,required" json:"status_code,required" query:"status_code,required"` StatusText string `form:"status_text,required" json:"status_text,required" query:"status_text,required"` TaskId string `form:"task_id,required" json:"task_id,required" query:"task_id,required"` Token string `form:"token,required" json:"token,required" query:"token,required"` ExpiresAt int64 `form:"expires_at,required" json:"expires_at,required" query:"expires_at,required"` } // volcengine client wrapper const ( // sami open api info DefaultRegion = "cn-north-1" ServiceVersion20210727 = "2021-07-27" ServiceName = "sami" ) var ( // DefaultInstance 默认的实例 DefaultInstance = NewInstance() ServiceInfo = &base.ServiceInfo{ Timeout: 10 * time.Second, Host: "open.volcengineapi.com", Header: http.Header{"Accept": []string{"application/json"}}, Credentials: base.Credentials{Region: "cn-north-1", Service: ServiceName}, } ApiInfoList = map[string]*base.ApiInfo{ "GetToken": { Method: http.MethodPost, Path: "/", Query: url.Values{ "Action": []string{"GetToken"}, "Version": []string{ServiceVersion20210727}, }, }, } ) // Sami . type Sami struct { Client *base.Client } // NewInstance new Sami client instance func NewInstance() *Sami { instance := &Sami{} instance.Client = base.NewClient(ServiceInfo, ApiInfoList) instance.Client.ServiceInfo.Credentials.Service = ServiceName instance.Client.ServiceInfo.Credentials.Region = DefaultRegion return instance } func (p *Sami) commonHandler(api string, form url.Values, resp interface{}) (int, error) { respBody, statusCode, err := p.Client.Post(api, form, nil) fmt.Println(string(respBody)) if err != nil { errMsg := err.Error() // business error will be shown in resp, request error should be nil here if errMsg[:3] != "api" { return statusCode, err } } if err := json.Unmarshal(respBody, resp); err != nil { return statusCode, err } return statusCode, nil } func (p *Sami) commonHandlerJSON(api string, jsonBody string, resp interface{}) (int, error) { respBody, statusCode, err := p.Client.Json(api, nil, jsonBody) fmt.Println(string(respBody)) if err != nil { errMsg := err.Error() // business error will be shown in resp, request error should be nil here if errMsg[:3] != "api" { return statusCode, err } } if err := json.Unmarshal(respBody, resp); err != nil { return statusCode, err } return statusCode, nil }
package com.sami; import com.alibaba.fastjson.JSONObject; import okhttp3.*; import org.apache.commons.codec.binary.Hex; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.TimeZone; public class TokenDemo { private static final String TIME_FORMAT_V4 = "yyyyMMdd'T'HHmmss'Z'"; private static final TimeZone tz = TimeZone.getTimeZone("UTC"); private static String getCurrentFormatDate() { DateFormat df = new SimpleDateFormat(TIME_FORMAT_V4); df.setTimeZone(tz); return df.format(new Date()); } public static String hashSHA256(byte[] content) throws Exception { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] result = md.digest(content); return Hex.encodeHexString(result); } catch (Exception e) { throw new Exception( "Unable to compute hash while signing request: " + e.getMessage(), e); } } public static byte[] hmacSHA256(byte[] key, String content) throws Exception { try { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); return mac.doFinal(content.getBytes()); } catch (Exception e) { throw new Exception( "Unable to calculate a request signature: " + e.getMessage(), e); } } public static void main(String args[]) throws Exception { String ak = "your_access_key"; String sk = "your_secret_key"; String appKey = "your_appkey"; String authVersion = "volc-auth-v1"; String region = "cn-north-1"; String service = "sami"; int expiration = 36000; String request = "request"; String algorithm = "HMAC-SHA256"; String action = "GetToken"; String version = "2021-07-27"; String method = "POST"; String contentType = "application/json; charset=utf-8"; String host = "open.volcengineapi.com"; String query = "Action=" + action + "&Version=" + version; String path = "/"; String url = "http://" + host + path + "?" + query; System.out.println("url: " + url); String format_date = getCurrentFormatDate(); String date = format_date.substring(0, 8); HashMap<String, String> headers = new HashMap<>(); JSONObject bodyObj = new JSONObject(); bodyObj.put("appkey", appKey); bodyObj.put("token_version", authVersion); bodyObj.put("expiration", expiration); String bodyStr = bodyObj.toJSONString(); System.out.println("body: " + bodyStr); String bodyHash256 = hashSHA256(bodyStr.getBytes(StandardCharsets.UTF_8)); System.out.println("body hash sha256: " + bodyHash256); headers.put("Host", host); headers.put("Content-Type", contentType); headers.put("X-Date", format_date); headers.put("X-Content-Sha256", bodyHash256); String[] sortedKeys = headers.keySet().toArray(new String[] {}); Arrays.sort(sortedKeys); StringBuilder canonicalizedQueryString = new StringBuilder(); for (String key : sortedKeys) { canonicalizedQueryString.append(key.toLowerCase()) .append(":") .append(headers.get(key)).append("\n"); } String signed_str = canonicalizedQueryString.toString(); System.out.println("signed_str: " + signed_str); StringBuilder signedHeadersBuilder = new StringBuilder(); for (String key : sortedKeys) { signedHeadersBuilder.append(";").append(key.toLowerCase()); } String signed_headersStr = signedHeadersBuilder.toString().substring(1); System.out.println("signed_headersStr: " + signed_headersStr); StringBuilder canoncialRequestBulder = new StringBuilder(); canoncialRequestBulder.append(method).append("\n"); canoncialRequestBulder.append(path).append("\n"); canoncialRequestBulder.append(query).append("\n"); canoncialRequestBulder.append(signed_str).append("\n"); canoncialRequestBulder.append(signed_headersStr).append("\n"); canoncialRequestBulder.append(bodyHash256); String canoncial_request = canoncialRequestBulder.toString(); System.out.println("canoncial_request: " + canoncial_request); String hashed_canon_req = hashSHA256(canoncial_request.getBytes(StandardCharsets.UTF_8)); System.out.println("hashed_canon_req: " + hashed_canon_req); StringBuilder credential_scopeBuilder = new StringBuilder(); credential_scopeBuilder.append(date).append("/"); credential_scopeBuilder.append(region).append("/"); credential_scopeBuilder.append(service).append("/"); credential_scopeBuilder.append(request); String credential_scope = credential_scopeBuilder.toString(); System.out.println("credential_scope: " + credential_scope); StringBuilder signingStrBuilder = new StringBuilder(); signingStrBuilder.append(algorithm).append("\n"); signingStrBuilder.append(format_date).append("\n"); signingStrBuilder.append(credential_scope).append("\n"); signingStrBuilder.append(hashed_canon_req); String signing_str = signingStrBuilder.toString(); System.out.println("signing_str: " + signing_str); byte[] kDate = hmacSHA256(sk.getBytes(StandardCharsets.UTF_8), date); byte[] kRegion = hmacSHA256(kDate, region); byte[] kService = hmacSHA256(kRegion, service); byte[] signing_key = hmacSHA256(kService, request); System.out.println("signing_key: " + signing_key); String sign = Hex.encodeHexString(hmacSHA256(signing_key, signing_str)); System.out.println("sign: " + sign); String credential = ak + "/" + credential_scope; System.out.println("credential: " + credential); String Authorization = algorithm + " Credential=" + credential + ", SignedHeaders=" + signed_headersStr + ", Signature=" + sign; System.out.println("Authorization: " + Authorization); RequestBody reqBody = RequestBody.create(MediaType.parse(contentType), bodyStr); Request request1 = new Request.Builder().url(url) .addHeader("Host", host) .addHeader("Content-Type", contentType) .addHeader("X-Date", format_date) .addHeader("X-Content-Sha256", bodyHash256) .addHeader("Authorization", Authorization) .post(reqBody) .build(); try { OkHttpClient client = new OkHttpClient(); Response response = client.newCall(request1).execute(); String body = response.body().string(); System.out.println(body); } catch (Exception e) { e.printStackTrace(); } } }
<?php /********** * * 获取Token demo * */ const ISO8601_BASIC = "Ymd\THis\Z"; // 设置 ak、sk、appkey信息 $accessKey = "your_access_key"; $secretKey = "your_secret_key"; $appKey = "your_appkey"; $host = "open.volcengineapi.com"; $authVersion = "volc-auth-v1"; $region = "cn-north-1"; $service = "sami"; // token的有效期,不能超过一天 $expiration = 36000; $request = "request"; $algorithm = "HMAC-SHA256"; $action = "GetToken"; $version = "2021-07-27"; $method = "POST"; $path = "/"; $contentType = "application/json; charset=utf-8"; $query = "Action=" . $action . "&Version=" . $version; $url = "https://" . $host . $path . "?" . $query; print "url: " . $url . "\n"; $format_date = gmdate(ISO8601_BASIC); $date = substr($format_date, 0, 8); $body = array( "appkey" => $appKey, "token_version" => $authVersion, "expiration" => $expiration ); $bodyObj = json_encode($body); // hashSHA256 编码 $bodyHash256 = hash("sha256", $bodyObj); print "body hash sha256: " . $bodyHash256 . "\n"; $headers = array( "Host" => $host, "Content-Type" => $contentType, "X-Date" => $format_date, "X-Content-Sha256" => $bodyHash256 ); // 按照字典顺序排序 ksort($headers); // head key 组合 $signed_headersStr = ""; // head kev-value 组合 $signed_str = ""; foreach ($headers as $x=>$x_value) { $signed_headersStr = $signed_headersStr . ";" . strtolower($x); $signed_str = $signed_str . strtolower($x) . ":" . $x_value . "\n"; } $signed_headersStr = substr($signed_headersStr, 1); print "signed_headersStr: " . $signed_headersStr . "\n"; print "signed_str: " . $signed_str . "\n"; $canoncial_request = $method . "\n" . $path . "\n" . $query . "\n" . $signed_str . "\n" . $signed_headersStr . "\n" . $bodyHash256; print "canoncial_request: " . $canoncial_request . "\n"; $hashed_canon_req = hash("sha256", $canoncial_request); print "hashed_canon_req: " . $hashed_canon_req . "\n"; $credential_scope = $date . "/" . $region . "/" . $service . "/" . $request; print "credential_scope: " . $credential_scope . "\n"; // 待签名的字符串 $signing_str = $algorithm . "\n" . $format_date . "\n" . $credential_scope . "\n" . $hashed_canon_req; print "signing_str: " . $signing_str . "\n"; // 生成待签名的密钥 $kDate = hash_hmac("sha256", $date, $secretKey, TRUE); $kRegion = hash_hmac("sha256", $region, $kDate, TRUE); $kService = hash_hmac("sha256", $service, $kRegion, TRUE); $signing_key = (hash_hmac("sha256", $request, $kService, TRUE)); // 签名结果 $sign = bin2hex(hash_hmac("sha256", $signing_str, $signing_key, TRUE)); print "sign: " . $sign . "\n"; $credential = $accessKey . "/" . $credential_scope; print "credential: " . $credential . "\n"; $Authorization = $algorithm . " Credential=" . $credential . ", SignedHeaders=" . $signed_headersStr . ", Signature=" . $sign; print "Authorization: " . $Authorization . "\n"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, TRUE); $httpHeaders = array( "Host:" . $host, "Content-Type:" . $contentType, "X-Date:" . $format_date, "X-Content-Sha256:" . $bodyHash256, "Authorization:" . $Authorization ); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders); curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyObj); curl_setopt($ch, CURLOPT_HEADER, TRUE); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $response = curl_exec($ch); if($response == FALSE) { print "curl_exec failed\n"; print curl_error($ch); return ; } $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $responseHeaders = substr($response, 0, $headerSize); $responseBody = substr($response, $headerSize); curl_close($ch); print "response head: " . $responseHeaders . "\n"; print "response body: " . $responseBody . "\n"; if ($httpCode != 200) { return ; } $resultObj = json_decode($responseBody, TRUE); $statusCode = $resultObj["status_code"]; print "status code: " .strval($statusCode) . "\n"; if ($statusCode == 20000000) { $token = $resultObj["token"]; $expires_at = $resultObj["expires_at"]; print "token: " . $token . "\n"; print "expire time: " . $expires_at . "\n"; } ?>
const openai = require("@volcengine/openapi"); const Service = openai.Service const appKey = 'your_appkey' const accessKeyId = 'your_access_key' const secretKey = 'your_secret_key' const hostname = 'open.volcengineapi.com' const path = "/"; const action = "GetToken"; const version = "2021-07-27"; const region = "cn-north-1" const authVersion = "volc-auth-v1"; const serviceName = 'sami' const expiration = 36000; export const getToken = async () => { const options = { accessKeyId: accessKeyId, secretKey: secretKey, // sessionToken:sessionToken, region: region, host: hostname, serviceName: serviceName, defaultVersion: version, } const samiService = new Service(options); const bodyObj = { token_version: authVersion, appkey: appKey, expiration, } const res = await samiService.fetchOpenAPI({ pathname: path, Action: action, Version: version, method: "POST", headers: { 'content-type': 'application/json', }, data: JSON.stringify(bodyObj) }) if (!res.token) { throw new Error(res.msg ?? '获取token失败') } return res.token }
特定状态码
HTTP状态码 | 业务状态码 | 错误信息 | 错误说明 | 解决办法 |
---|---|---|---|---|
420 | 无 | generate token failed, reason: replay attack | 两次申请token请求间隔小于60秒,被判定为重复攻击 | 建议在有效期内使用同一token,避免重复申请 |
420 | 无 | generate token failed | 使用appkey不存在;或者当前用户与使用appkey不对应,比如主账号创建的应用、误使用子账号的密钥进行操作 | 检查access key和appkey是否匹配,不允许混用,服务端有严格检查 |
从安全角度为防止用户Ak,SK等信息泄露,不建议用户在客户端直接申请token,需要用户在其自己的服务端向火山引擎的网关申请一个token,将其下发给应用。另外请求不能过于频繁(间隔不小于60秒),火山引擎的网关都会认为是重复攻击,导致获取token失败。
下面提供了一个例子:
默认更新间隔 3600*24s
src/main/java/com/bytedance/sami/token_caching/TokenService.java第61行的expiration变量值
前置条件:
安装JDK 11
安装maven
步骤:
下载源码
构建包
mvn package -DskipTests -B
# 设置appkey/ak/sk export SAMI_APPKEY="" export SAMI_AK="" export SAMI_SK="" java -jar target/token-caching-1.0.0-SNAPSHOT-fat.jar
curl [http://localhost:8888/token](http://localhost:8888/token) # 期待输出: # 类似:{"token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDEzODU3MjEsImlhdCI6MTY0MTM4NTY2MSwiaXNzIjoiU0FNSSBNZXRhIiwidmVyc2lvbiI6InZvbGMtYXV0aC12MSIsImFjY291bnRfaWQiOjIxMDAwNTE3MTIsImFjY291bnRfbmFtZSI6IjIxMDAwNTE3MTIiLCJhcHBfaWQiOjQxLCJhcHBfbmFtZSI6Iue8lui-keahhuaetiIsImFwcGtleSI6ImdUc0RrQUxQSWUiLCJzZXJ2aWNlIjoic2FtaSIsInNvdXJjZSI6IkFjY291bnQiLCJyZWdpb24iOiJjbi1ub3J0aC0xIn0.BQ-M19E8sM8gEFukuzNor8tI6x8JXWOSJYBA9gzWaRY83rSAxHwxjkBUojkCEK8u_SCz0mBbXRXjKqs_4MfeiQ"}
java -cp target/token-caching-1.0.0-SNAPSHOT-fat.jar com.bytedance.sami.TokenDemo
前置条件:
步骤:
(耗时比较长)
docker build -t sami-token:latest .
# 设置对应的AppKey,AK,SK docker run --rm -e SAMI_APPKEY="" -e SAMI_AK="" -e SAMI_SK="" -p 8888:8888 sami-token:latest
curl [http://localhost:8888/token](http://localhost:8888/token) # 期待输出: # 类似:{"token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDEzODU3MjEsImlhdCI6MTY0MTM4NTY2MSwiaXNzIjoiU0FNSSBNZXRhIiwidmVyc2lvbiI6InZvbGMtYXV0aC12MSIsImFjY291bnRfaWQiOjIxMDAwNTE3MTIsImFjY291bnRfbmFtZSI6IjIxMDAwNTE3MTIiLCJhcHBfaWQiOjQxLCJhcHBfbmFtZSI6Iue8lui-keahhuaetiIsImFwcGtleSI6ImdUc0RrQUxQSWUiLCJzZXJ2aWNlIjoic2FtaSIsInNvdXJjZSI6IkFjY291bnQiLCJyZWdpb24iOiJjbi1ub3J0aC0xIn0.BQ-M19E8sM8gEFukuzNor8tI6x8JXWOSJYBA9gzWaRY83rSAxHwxjkBUojkCEK8u_SCz0mBbXRXjKqs_4MfeiQ"}