You need to enable JavaScript to run this app.
导航
服务鉴权-获取Token
最近更新时间:2024.05.28 11:16:15首次发布时间:2021.08.23 16:15:12

功能介绍

使用服务账号调用火山引擎OpenAPI,获取SAMI音频技术API的服务鉴权Token。

接口说明

请求地址

open.volcengineapi.com

请求参数

HTTP请求Content-Type: application/json

字段描述类型是否必传默认值
Service音频技术对应填samistring-
Region访问地区,填cn-north-1string-
access_key获取方式:用户指南-获取访问密钥string-
secret_key获取方式:用户指南-获取访问密钥string-
token_version
volc-auth-v1string-
appkey服务接入appkey。在音频技术控制台创建应用后获得string-
expirationtoken 的过期时间,单位是秒,可以自行定义,但不能超过 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签名调用指南 来自行实现,如果遇到任何问题请联系技术支持。

Golang

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
}

Java

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

<?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";
}

?>

Nodejs

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状态码业务状态码错误信息错误说明解决办法
420generate token failed, reason: replay attack两次申请token请求间隔小于60秒,被判定为重复攻击建议在有效期内使用同一token,避免重复申请
420generate token failed使用appkey不存在;或者当前用户与使用appkey不对应,比如主账号创建的应用、误使用子账号的密钥进行操作检查access key和appkey是否匹配,不允许混用,服务端有严格检查

附录

token缓冲服务参考示例

从安全角度为防止用户Ak,SK等信息泄露,不建议用户在客户端直接申请token,需要用户在其自己的服务端向火山引擎的网关申请一个token,将其下发给应用。另外请求不能过于频繁(间隔不小于60秒),火山引擎的网关都会认为是重复攻击,导致获取token失败。
下面提供了一个例子:

tob-token-caching-master.zip
90.43KB

默认更新间隔 3600*24s
src/main/java/com/bytedance/sami/token_caching/TokenService.java第61行的expiration变量值

一、直接运行

前置条件

  1. 安装JDK 11

  2. 安装maven

步骤

  1. 下载源码

  2. 构建包

mvn package -DskipTests -B
  1. 运行服务
# 设置appkey/ak/sk
export SAMI_APPKEY=""
export SAMI_AK=""
export SAMI_SK=""

java -jar target/token-caching-1.0.0-SNAPSHOT-fat.jar
  1. 验证生成的token
curl [http://localhost:8888/token](http://localhost:8888/token)

# 期待输出:
# 类似:{"token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDEzODU3MjEsImlhdCI6MTY0MTM4NTY2MSwiaXNzIjoiU0FNSSBNZXRhIiwidmVyc2lvbiI6InZvbGMtYXV0aC12MSIsImFjY291bnRfaWQiOjIxMDAwNTE3MTIsImFjY291bnRfbmFtZSI6IjIxMDAwNTE3MTIiLCJhcHBfaWQiOjQxLCJhcHBfbmFtZSI6Iue8lui-keahhuaetiIsImFwcGtleSI6ImdUc0RrQUxQSWUiLCJzZXJ2aWNlIjoic2FtaSIsInNvdXJjZSI6IkFjY291bnQiLCJyZWdpb24iOiJjbi1ub3J0aC0xIn0.BQ-M19E8sM8gEFukuzNor8tI6x8JXWOSJYBA9gzWaRY83rSAxHwxjkBUojkCEK8u_SCz0mBbXRXjKqs_4MfeiQ"}
  1. 单独运行示例代码
java -cp target/token-caching-1.0.0-SNAPSHOT-fat.jar com.bytedance.sami.TokenDemo

二、从docker运行

前置条件

  1. 安装Docker desktop

步骤

  1. 构建docker image

(耗时比较长)

docker build -t sami-token:latest .
  1. 运行服务
# 设置对应的AppKey,AK,SK
docker run --rm -e SAMI_APPKEY="" -e SAMI_AK="" -e SAMI_SK="" -p 8888:8888 sami-token:latest
  1. 验证生成的token
curl [http://localhost:8888/token](http://localhost:8888/token)

# 期待输出:
# 类似:{"token":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDEzODU3MjEsImlhdCI6MTY0MTM4NTY2MSwiaXNzIjoiU0FNSSBNZXRhIiwidmVyc2lvbiI6InZvbGMtYXV0aC12MSIsImFjY291bnRfaWQiOjIxMDAwNTE3MTIsImFjY291bnRfbmFtZSI6IjIxMDAwNTE3MTIiLCJhcHBfaWQiOjQxLCJhcHBfbmFtZSI6Iue8lui-keahhuaetiIsImFwcGtleSI6ImdUc0RrQUxQSWUiLCJzZXJ2aWNlIjoic2FtaSIsInNvdXJjZSI6IkFjY291bnQiLCJyZWdpb24iOiJjbi1ub3J0aC0xIn0.BQ-M19E8sM8gEFukuzNor8tI6x8JXWOSJYBA9gzWaRY83rSAxHwxjkBUojkCEK8u_SCz0mBbXRXjKqs_4MfeiQ"}