跳到主要内容
版本:v4

TapTap OAuth 接口

概述​

TapTap OpenAPI 采用 MAC Token 签名来验证 API 请求的身份。

基本概念​

  • Access Token:用户通过 SDK 完成 TapTap 登录授权后,SDK 返回的凭证对象,包含 kid、mac_key 等字段。
  • MAC Token:使用 Access Token 中的字段,按照 HMAC-SHA1 算法签算生成的 Authorization 请求头,用于调用 TapTap OpenAPI 时的身份验证。
  • openid:TapTap 用户 + Client ID = openid(同一用户在不同游戏中 openid 不同)
  • unionid:TapTap 用户 + 开发者账号 = unionid(同一用户在同一开发者的所有游戏中 unionid 相同)

字段命名对照​

SDK 与 Authorization 头中对同一字段使用了不同的命名,下面给出对应关系:

SDK Access Token 字段Authorization 头中的名称说明
kidMAC id="..." 中的 id用于标识本次授权的 Access Token,每次登录授权后由 SDK 返回
mac_key(不出现在请求头中)用于 HMAC-SHA1 签算的密钥,每次登录授权后由 SDK 返回。注意与开发者控制台中的 Server Secret 是不同的值

Access Token​

开发者接入 SDK 登录模块,用户授权应用后即可获取 Access Token。游戏移动端可通过 GetCurrentTapAccount 方法获取当前有效的 Access Token。

Access Token 包含以下字段:

public String kid; // Access Token 标识符,用于 Authorization 头中的 MAC id 字段
public String token_type; // Token 类型,如 "mac"
public String mac_key; // 用于 HMAC-SHA1 签算的密钥
public String mac_algorithm; // 签算算法,如 "hmac-sha-1"
public Set<String> scopes; // 授权范围,如 "basic_info" 或 "public_profile"
  • 单个 Access Token 的最长有效期为 30 天。但在某些情况下,Access Token 可能会提前失效,例如用户注销账号、用户在 TapTap 客户端解除授权等。接入方需处理 Token 失效的相关场景,避免使用已吊销的 Token 发起请求。
  • SDK 在每次应用启动时会自动刷新 Access Token,确保用户会话的持续有效性。
  • 注意:不建议在服务端或游戏客户端缓存 Access Token。需要使用时,应通过 SDK 获取最新的 Access Token。

MAC Token 签算方法见 MAC Token 签算 部分。

API​

当 SDK 只请求 basic_info 的权限时,请使用基础信息接口,请求 public_profile 时,请使用详细信息接口。

获取当前账户基础信息​

GET https://open.tapapis.com/account/basic-info/v1?client_id=xxx
Authorization mac token

请求参数​

字段类型说明
client_idstring该应用的 Client ID,应与约定相同

响应参数​

字段类型说明
openidstring授权用户唯一标识,每个玩家在每个游戏中的 openid 都是不一样的,同一游戏获取同一玩家的 openid 总是相同
unionidstring授权用户唯一标识,一个玩家在一个厂商的所有游戏中 unionid 都是一样的,不同厂商 unionid 不同

请求示例​

替换其中的 kid(即 Authorization 头中 MAC id="..." 的值)、mac_key(用于签算)和 Client ID 为实际值。

curl -s -H 'Authorization:MAC id="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJa
gCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTs
KQ",ts="1618221750",nonce="adssd",mac="XWTPmq6A6LzgK8BbNDwj+kE4gzs="' "https://open.tapapis.com/account/basic-info/v1?client_id=<Client ID>"

获取当前账户详细信息​

GET https://open.tapapis.com/account/profile/v1?client_id=xxx
Authorization mac token

请求参数​

字段类型说明
client_idstring该应用的 Client ID,应与约定相同

响应参数​

字段类型说明
namestring用户名
avatarstring用户头像图片地址
openidstring授权用户唯一标识,每个玩家在每个游戏中的 openid 都是不一样的,同一游戏获取同一玩家的 openid 总是相同
unionidstring授权用户唯一标识,一个玩家在一个厂商的所有游戏中 unionid 都是一样的,不同厂商 unionid 不同

请求示例​

替换其中的 kid(即 Authorization 头中 MAC id="..." 的值)、mac_key(用于签算)和 Client ID 为实际值。

curl -s -H 'Authorization:MAC id="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJa
gCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTs
KQ",ts="1618221750",nonce="adssd",mac="XWTPmq6A6LzgK8BbNDwj+kE4gzs="' "https://open.tapapis.com/account/profile/v1?client_id=<Client ID>"

MAC Token 签算​

签算所需的 kid 和 mac_key 均来自客户端 SDK 登录后返回的 Access Token,字段含义见字段命名对照。

签算步骤​

  1. 构造待签名字符串,格式如下(每个字段之间用 \n 分隔):

    {timestamp}\n{nonce}\n{method}\n{uri}\n{host}\n{port}\n\n
    • timestamp:当前时间戳(秒级)
    • nonce:随机字符串
    • method:HTTP 请求方法(如 GET、POST)
    • uri:请求路径(包含 query string,如 /account/profile/v1?client_id=xxx)
    • host:请求域名(如 open.tapapis.com)
    • port:端口号(HTTPS 为 443)
  2. 使用 mac_key 对待签名字符串进行 HMAC-SHA1 签算,并将结果进行 Base64 编码,得到 mac 值。

  3. 构造 Authorization 头:

    MAC id="{kid}",ts="{timestamp}",nonce="{nonce}",mac="{mac}"

签算示例代码​

Node.js 请求示例
const http = require('http');
const https = require('https');
const crypto = require('crypto');

/**
* 以下是配置项:请按实际情况替换参数后进行授权请求。
*/
const client_id = "请替换为控制台的 Client ID";
const kid = "请替换为客户端 SDK 授权成功后,Access Token 中的 kid";
const macKey = "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key";

/**
* 获取当前账户详细信息的接口。
* 如果需要获取账户基础信息,请替换为以下 URL:
* https://open.tapapis.com/account/basic-info/v1
*/
const url = "https://open.tapapis.com/account/profile/v1";

/**
* 主程序逻辑
*/
// 步骤 1:设置请求方法和 URL
const method = 'GET';
const requestUrl = url + '?client_id=' + client_id;

// 步骤 2:生成时间戳和随机数
const ts = Math.floor(Date.now() / 1000).toString().padStart(10, '0');
const nonce = getRandomString(5);

// 步骤 3:创建待签名字符串并生成签名
const parsedUrl = new URL(requestUrl);
const host = parsedUrl.hostname;
const uri = parsedUrl.pathname + parsedUrl.search;
const port = parsedUrl.port || (parsedUrl.protocol === 'https:' ? '443' : '80');
const signingString = buildSigningString(ts, nonce, method, uri, host, port);
const mac = sign(signingString, macKey);

// 步骤 4:生成 Authorization 头
const authorization = `MAC id="${kid}",ts="${ts}",nonce="${nonce}",mac="${mac}"`;
console.log('Authorization: ' + authorization);

// 步骤 5:执行 HTTP 请求并输出结果
const client = parsedUrl.protocol === 'https:' ? https : http;
const req = client.request({
hostname: host,
port: port,
path: uri,
method: method,
headers: { 'Authorization': authorization }
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => { console.log(data); });
});
req.end();

/**
* 生成随机字符串
* @param {number} length 随机字符串的长度
* @returns {string} 随机生成的字符串(仅含大小写字母和数字)
*/
function getRandomString(length) {
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const bytes = crypto.randomBytes(length);
let result = '';
for (let i = 0; i < length; i++) {
result += chars[bytes[i] % chars.length];
}
return result;
}

/**
* 构造待签名字符串
* @param {string} ts 时间戳
* @param {string} nonce 随机数
* @param {string} method HTTP 方法
* @param {string} uri 请求路径(含 query string)
* @param {string} host 请求域名
* @param {string} port 端口号
* @returns {string} 待签名字符串
*/
function buildSigningString(ts, nonce, method, uri, host, port) {
return `${ts}\n${nonce}\n${method}\n${uri}\n${host}\n${port}\n\n`;
}

/**
* 使用 HMAC-SHA1 生成签名
* @param {string} signingString 待签名字符串
* @param {string} key MAC 密钥
* @returns {string} Base64 编码的签名值
*/
function sign(signingString, key) {
const hmac = crypto.createHmac('sha1', key);
hmac.update(signingString);
return hmac.digest('base64');
}
Java 请求示例
package com.taptap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class Authorization {
public static void main(String[] args) throws IOException {
/**
* 以下是配置项:请按实际情况替换参数后进行授权请求。
*/
String client_id = "请替换为控制台的 Client ID";
String kid = "请替换为客户端 SDK 授权成功后,Access Token 中的 kid";
String mac_key = "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key";

/**
* 获取当前账户详细信息的接口。
* 如果需要获取账户基础信息,请替换为以下 URL:
* https://open.tapapis.com/account/basic-info/v1
*/
String url = "https://open.tapapis.com/account/profile/v1";

/**
* 主程序逻辑
*/
// 步骤 1:设置请求方法和 URL
String method = "GET";
String request_url = url + "?client_id=" + client_id;

// 步骤 2:生成时间戳和随机数
String ts = String.format(Locale.US, "%010d", System.currentTimeMillis() / 1000);
String nonce = getRandomString(5);

// 步骤 3:创建待签名字符串并生成签名
URL parsedUrl = new URL(request_url);
String host = parsedUrl.getHost();
String uri = request_url.substring(request_url.lastIndexOf(host) + host.length());
String port = request_url.startsWith("https") ? "443" : "80";
String signingString = buildSigningString(ts, nonce, method, uri, host, port);
String mac = sign(signingString, mac_key);

// 步骤 4:生成 Authorization 头
String authorization = String.format("MAC id=\"%s\",ts=\"%s\",nonce=\"%s\",mac=\"%s\"",
kid, ts, nonce, mac);
System.out.println("Authorization: " + authorization);

// 步骤 5:执行 HTTP 请求并输出结果
HttpURLConnection conn = (HttpURLConnection) new URL(request_url).openConnection();
conn.setRequestProperty("Authorization", authorization);
conn.setRequestMethod(method);
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuilder result = new StringBuilder();
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
System.out.println(result.toString());
}

/**
* 生成随机字符串
* @param length 随机字符串的长度
* @return 随机生成的字符串(仅含大小写字母和数字)
*/
private static String getRandomString(int length) {
String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
SecureRandom random = new SecureRandom();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}

/**
* 构造待签名字符串
* @param ts 时间戳
* @param nonce 随机数
* @param method HTTP 方法
* @param uri 请求路径(含 query string)
* @param host 请求域名
* @param port 端口号
* @return 待签名字符串
*/
private static String buildSigningString(String ts, String nonce, String method,
String uri, String host, String port) {
return ts + "\n" + nonce + "\n" + method + "\n" + uri + "\n" + host + "\n" + port + "\n\n";
}

/**
* 使用 HMAC-SHA1 生成签名
* @param signingString 待签名字符串
* @param key MAC 密钥
* @return Base64 编码的签名值
*/
private static String sign(String signingString, String key) {
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(signingString.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException(e);
}
}
}
PHP 请求示例
<?php
/**
* 以下是配置项:请按实际情况替换参数后进行授权请求。
*/
$client_id = "请替换为控制台的 Client ID";
$kid = "请替换为客户端 SDK 授权成功后,Access Token 中的 kid";
$mac_key = "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key";

/**
* 获取当前账户详细信息的接口。
* 如果需要获取账户基础信息,请替换为以下 URL:
* https://open.tapapis.com/account/basic-info/v1
*/
$url = "https://open.tapapis.com/account/profile/v1";

/**
* 主程序逻辑
*/
// 步骤 1:设置 $method、$request_url
$method = "GET";
$request_url = $url . "?client_id=" . urlencode($client_id);

// 步骤 2:生成时间戳和随机数
$ts = time(); // 秒级当前时间戳
$nonce = randomString(5); // 随机数,至少5位

// 步骤 3:创建待签名字符串并生成签名
$signing_string = createSigningString($request_url, $ts, $nonce, $method);
$mac = sign($signing_string, $mac_key);

// 步骤 4:生成 Authorization 头部信息
$auth = sprintf('MAC id="%s",ts="%s",nonce="%s",mac="%s"', $kid, $ts, $nonce, $mac);
echo "Authorization: " . $auth . PHP_EOL . PHP_EOL;

// 步骤 5:执行 HTTP 请求并输出结果
$headers = array("Authorization: " . $auth);
$response = executeCurlRequest($request_url, $headers, $method);

echo "HTTP Status Code: " . $response['http_code'] . PHP_EOL . PHP_EOL;
if (isset($response['error'])) {
echo "Error: " . $response['error'] . PHP_EOL . PHP_EOL;
}
if (isset($response['body'])) {
echo "Response Body: " . PHP_EOL . json_encode($response['body'], JSON_PRETTY_PRINT) . PHP_EOL;
}

/**
* 创建待签名字符串
*
* @param string $request_url 请求的 URL
* @param int $ts 时间戳
* @param string $nonce 随机数
* @param string $method HTTP 方法
* @return string 待签名字符串
*/
function createSigningString($request_url, $ts, $nonce, $method)
{
$parsed_url = parse_url($request_url);
$uri = $parsed_url['path'] . '?' . $parsed_url['query'];
$domain = $parsed_url['host'];
$port = 443; // 使用 HTTPS 固定端口

return implode("\n", [
$ts,
$nonce,
$method,
$uri,
$domain,
$port,
""
]) . "\n";
}

/**
* 生成签名值
*
* @param string $signing_string 待签名字符串
* @param string $mac_key MAC 密钥
* @return string 生成的签名值
* @example sign('abc', 'def') -> dYTuFEkwcs2NmuhQ4P8JBTgjD4w=
*/
function sign($signing_string, $mac_key)
{
return base64_encode(hash_hmac('sha1', $signing_string, $mac_key, true));
}

/**
* 执行 cURL 请求并返回结果
*
* @param string $url 请求的 URL
* @param array $headers 请求头
* @param string $method HTTP 方法
* @return array 包含状态码和响应内容或错误信息
*/
function executeCurlRequest($url, $headers, $method)
{
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

// 执行请求并捕获响应
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);

// 检查是否发生 cURL 错误
if (curl_errno($curl)) {
$error_message = curl_error($curl);
curl_close($curl);

return array(
'http_code' => 0,
'error' => "cURL 错误: " . $error_message,
'body' => json_decode($response, true)
);
}

// 关闭 cURL 句柄
curl_close($curl);

// 解析响应
$parsed_body = json_decode($response, true);

// 根据 HTTP 状态码返回结果
if ($httpCode >= 200 && $httpCode < 300) {
return array(
'http_code' => $httpCode,
'body' => $parsed_body
);
} else {
return array(
'http_code' => $httpCode,
'error' => "HTTP 请求失败,状态码: $httpCode",
'body' => $parsed_body
);
}
}

/**
* 生成随机字符串
*
* @param int $length 随机字符串的长度
* @return string 随机生成的字符串
*/
function randomString($length = 5)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';

for ($i = 0; $i < $length; $i++) {
$randomIndex = mt_rand(0, $charactersLength - 1);
$randomString .= $characters[$randomIndex];
}

return $randomString;
}
Python3 请求示例
import base64
import hmac
import random
import string
import time
from hashlib import sha1
from urllib.parse import urlparse
from urllib.request import Request, urlopen

#
# 以下是配置项:请按实际情况替换参数后进行授权请求。
#
client_id = "请替换为控制台的 Client ID"
kid = "请替换为客户端 SDK 授权成功后,Access Token 中的 kid"
mac_key = "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key"

#
# 获取当前账户详细信息的接口。
# 如果需要获取账户基础信息,请替换为以下 URL:
# https://open.tapapis.com/account/basic-info/v1
#
url = "https://open.tapapis.com/account/profile/v1"


#
# 主程序逻辑
#
def main():
# 步骤 1:设置请求方法和 URL
method = 'GET'
request_url = f'{url}?client_id={client_id}'

# 步骤 2:生成时间戳和随机数
ts = str(int(time.time()))
nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=5))

# 步骤 3:创建待签名字符串并生成签名
parsed = urlparse(request_url)
signing_string = build_signing_string(
ts, nonce, method, parsed.path + '?' + parsed.query, parsed.hostname, '443')
mac = sign(signing_string, mac_key)

# 步骤 4:生成 Authorization 头
authorization = f'MAC id="{kid}",ts="{ts}",nonce="{nonce}",mac="{mac}"'
print('Authorization:', authorization)

# 步骤 5:执行 HTTP 请求并输出结果
req = Request(request_url, headers={'Authorization': authorization})
with urlopen(req) as response:
print(response.read().decode('UTF-8'))


def build_signing_string(ts, nonce, method, uri, host, port):
"""构造待签名字符串

Args:
ts: 时间戳
nonce: 随机数
method: HTTP 方法
uri: 请求路径(含 query string)
host: 请求域名
port: 端口号

Returns:
待签名字符串
"""
return '\n'.join([ts, nonce, method, uri, host, port, '']) + '\n'


def sign(signing_string, key):
"""使用 HMAC-SHA1 生成签名

Args:
signing_string: 待签名字符串
key: MAC 密钥

Returns:
Base64 编码的签名值
"""
hmac_code = hmac.new(key.encode('UTF-8'), signing_string.encode('UTF-8'), sha1)
return base64.b64encode(hmac_code.digest()).decode('UTF-8')


if __name__ == '__main__':
main()
Go 请求示例
package main

import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"time"
)

func main() {
/*
* 以下是配置项:请按实际情况替换参数后进行授权请求。
*/
clientId := "请替换为控制台的 Client ID"
kid := "请替换为客户端 SDK 授权成功后,Access Token 中的 kid"
macKey := "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key"
reqHost := "open.tapapis.com"

/*
* 获取当前账户详细信息的接口。
* 如果需要获取账户基础信息,请将下方路径替换为:
* /account/basic-info/v1
*/
reqURI := "/account/profile/v1?client_id=" + clientId
reqURL := "https://" + reqHost + reqURI

/*
* 主程序逻辑
*/
// 步骤 1:设置请求方法和 URL
method := "GET"

// 步骤 2:生成时间戳和随机数
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
nonce := randomString(5)

// 步骤 3:创建待签名字符串并生成签名
signingString := buildSigningString(timestamp, nonce, method, reqURI, reqHost, "443")
mac := hmacSha1(signingString, macKey)

// 步骤 4:生成 Authorization 头
authorization := fmt.Sprintf(`MAC id="%s",ts="%s",nonce="%s",mac="%s"`, kid, timestamp, nonce, mac)
fmt.Println("Authorization:", authorization)

// 步骤 5:执行 HTTP 请求并输出结果
client := http.Client{}
req, err := http.NewRequest(method, reqURL, nil)
if err != nil {
fmt.Println(err.Error())
return
}
req.Header.Add("Authorization", authorization)
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(string(respBody))
}

// buildSigningString 构造待签名字符串
//
// 参数说明:
// - ts: 时间戳
// - nonce: 随机数
// - method: HTTP 方法
// - uri: 请求路径(含 query string)
// - host: 请求域名
// - port: 端口号
func buildSigningString(ts, nonce, method, uri, host, port string) string {
return ts + "\n" + nonce + "\n" + method + "\n" + uri + "\n" + host + "\n" + port + "\n\n"
}

// hmacSha1 使用 HMAC-SHA1 生成签名,返回 Base64 编码的签名值
//
// 参数说明:
// - signingString: 待签名字符串
// - key: MAC 密钥
func hmacSha1(signingString, key string) string {
mac := hmac.New(sha1.New, []byte(key))
mac.Write([]byte(signingString))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

// randomString 生成随机字符串
//
// 参数说明:
// - length: 随机字符串的长度
func randomString(length int) string {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
rand.Read(b)
for i := range b {
b[i] = chars[b[i]%byte(len(chars))]
}
return string(b)
}
C# 请求示例
using System.Collections;
using UnityEngine;
using System;
using System.Net.Http;
using System.Text;

public class TapLoginOAuth : MonoBehaviour
{
/**
* 以下是配置项:请按实际情况替换参数后进行授权请求。
*/
private string clientId = "请替换为控制台的 Client ID";
private string kid = "请替换为客户端 SDK 授权成功后,Access Token 中的 kid";
private string macKey = "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key";

/**
* 获取当前账户详细信息的接口。
* 如果需要获取账户基础信息,请替换为以下 URL:
* https://open.tapapis.com/account/basic-info/v1
*/
private string url = "https://open.tapapis.com/account/profile/v1";

void Start()
{
StartCoroutine(SendRequest());
}

IEnumerator SendRequest()
{
/**
* 主程序逻辑
*/
// 步骤 1:设置请求方法和 URL
string method = "GET";
string requestUrl = url + "?client_id=" + clientId;

// 步骤 2:生成时间戳和随机数
string ts = ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString();
string nonce = GenerateNonce(5);

// 步骤 3:创建待签名字符串并生成签名
var parsedUrl = new Uri(requestUrl);
string host = parsedUrl.Host;
string uri = parsedUrl.PathAndQuery;
string signingString = BuildSigningString(ts, nonce, method, uri, host, "443");
string mac = Sign(signingString, macKey);

// 步骤 4:生成 Authorization 头
string authorization = string.Format("MAC id=\"{0}\",ts=\"{1}\",nonce=\"{2}\",mac=\"{3}\"",
kid, ts, nonce, mac);
Debug.Log("Authorization: " + authorization);

// 步骤 5:执行 HTTP 请求并输出结果
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(requestUrl));
request.Headers.Add("Authorization", authorization);
var response = httpClient.SendAsync(request).Result;
var responseBody = response.Content.ReadAsStringAsync().Result;
Debug.Log("Response: " + responseBody);
}

yield return null;
}

/// <summary>
/// 构造待签名字符串
/// </summary>
/// <param name="ts">时间戳</param>
/// <param name="nonce">随机数</param>
/// <param name="method">HTTP 方法</param>
/// <param name="uri">请求路径(含 query string)</param>
/// <param name="host">请求域名</param>
/// <param name="port">端口号</param>
/// <returns>待签名字符串</returns>
string BuildSigningString(string ts, string nonce, string method, string uri, string host, string port)
{
string[] signArray = { ts, nonce, method, uri, host, port, "" };
return string.Join("\n", signArray) + "\n";
}

/// <summary>
/// 使用 HMAC-SHA1 生成签名
/// </summary>
/// <param name="signingString">待签名字符串</param>
/// <param name="key">MAC 密钥</param>
/// <returns>Base64 编码的签名值</returns>
string Sign(string signingString, string key)
{
using (var hmac = new System.Security.Cryptography.HMACSHA1(Encoding.UTF8.GetBytes(key)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingString));
return Convert.ToBase64String(hash);
}
}

/// <summary>
/// 生成随机字符串
/// </summary>
/// <param name="length">随机字符串的长度</param>
/// <returns>随机生成的字符串</returns>
string GenerateNonce(int length)
{
const string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var random = new System.Random();
var nonce = new char[length];
for (int i = 0; i < length; i++)
{
nonce[i] = chars[random.Next(chars.Length)];
}
return new string(nonce);
}
}
C++ 请求示例
#include <iostream>
#include <string>
#include <sstream>
#include <ctime>
#include <random>
#include <curl/curl.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>

/**
* 以下是配置项:请按实际情况替换参数后进行授权请求。
*/
const std::string client_id = "请替换为控制台的 Client ID";
const std::string kid = "请替换为客户端 SDK 授权成功后,Access Token 中的 kid";
const std::string mac_key = "请替换为客户端 SDK 授权成功后,Access Token 中的 mac_key";

/**
* 获取当前账户详细信息的接口。
* 如果需要获取账户基础信息,请替换为以下 URL:
* https://open.tapapis.com/account/basic-info/v1
*/
const std::string url = "https://open.tapapis.com/account/profile/v1";

// 函数声明
std::string createSigningString(const std::string& request_url, time_t ts,
const std::string& nonce, const std::string& method);
std::string sign(const std::string& signing_string, const std::string& key);
std::string randomString(int length);
std::string base64Encode(const unsigned char* input, unsigned int length);
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp);

int main()
{
/**
* 主程序逻辑
*/
// 步骤 1:设置 method、request_url
std::string method = "GET";
std::string request_url = url + "?client_id=" + client_id;

// 步骤 2:生成时间戳和随机数
time_t ts = time(nullptr); // 秒级当前时间戳
std::string nonce = randomString(5); // 随机数,至少5位

// 步骤 3:创建待签名字符串并生成签名
std::string signing_string = createSigningString(request_url, ts, nonce, method);
std::string mac = sign(signing_string, mac_key);

// 步骤 4:生成 Authorization 头部信息
std::ostringstream auth_stream;
auth_stream << "MAC id=\"" << kid << "\",ts=\"" << ts
<< "\",nonce=\"" << nonce << "\",mac=\"" << mac << "\"";
std::string auth = auth_stream.str();
std::cout << "Authorization: " << auth << std::endl << std::endl;

// 步骤 5:执行 HTTP 请求并输出结果
CURL* curl = curl_easy_init();
if (!curl) {
std::cerr << "Error: curl init failed" << std::endl;
return 1;
}

std::string response_body;
struct curl_slist* headers = curl_slist_append(nullptr, ("Authorization: " + auth).c_str());

curl_easy_setopt(curl, CURLOPT_URL, request_url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body);

CURLcode ret = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

std::cout << "HTTP Status Code: " << http_code << std::endl << std::endl;
if (ret != CURLE_OK) {
std::cerr << "Error: cURL error: " << curl_easy_strerror(ret) << std::endl;
}
std::cout << "Response Body: " << std::endl << response_body << std::endl;

curl_slist_free_all(headers);
curl_easy_cleanup(curl);

return 0;
}

/**
* 创建待签名字符串
*
* @param request_url 请求的 URL
* @param ts 时间戳
* @param nonce 随机数
* @param method HTTP 方法
* @return 待签名字符串
*/
std::string createSigningString(const std::string& request_url, time_t ts,
const std::string& nonce, const std::string& method)
{
// 解析 URL:提取域名、路径和查询参数
size_t scheme_end = request_url.find("://");
size_t host_start = (scheme_end != std::string::npos) ? scheme_end + 3 : 0;
size_t path_start = request_url.find('/', host_start);

std::string domain = request_url.substr(host_start, path_start - host_start);
std::string uri = request_url.substr(path_start);
int port = 443; // 使用 HTTPS 固定端口

std::ostringstream oss;
oss << ts << "\n" << nonce << "\n" << method << "\n"
<< uri << "\n" << domain << "\n" << port << "\n\n";

return oss.str();
}

/**
* 生成签名值
*
* @param signing_string 待签名字符串
* @param key MAC 密钥
* @return 生成的签名值
* @example sign("abc", "def") -> dYTuFEkwcs2NmuhQ4P8JBTgjD4w=
*/
std::string sign(const std::string& signing_string, const std::string& key)
{
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_len = 0;

HMAC(EVP_sha1(),
key.c_str(), key.length(),
reinterpret_cast<const unsigned char*>(signing_string.c_str()), signing_string.length(),
digest, &digest_len);

return base64Encode(digest, digest_len);
}

/**
* 生成随机字符串
*
* @param length 随机字符串的长度
* @return 随机生成的字符串
*/
std::string randomString(int length)
{
const std::string characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::default_random_engine generator(static_cast<unsigned long>(time(nullptr)));
std::uniform_int_distribution<int> distribution(0, characters.size() - 1);

std::string result;
for (int i = 0; i < length; ++i) {
result += characters[distribution(generator)];
}
return result;
}

/**
* Base64 编码
*
* @param input 输入数据
* @param length 输入数据长度
* @return Base64 编码后的字符串
*/
std::string base64Encode(const unsigned char* input, unsigned int length)
{
int encoded_size = 4 * ((length + 2) / 3);
std::string output(encoded_size + 1, '\0');
EVP_EncodeBlock(reinterpret_cast<unsigned char*>(&output[0]), input, length);
output.resize(encoded_size);
return output;
}

/**
* cURL 写回调函数
*/
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp)
{
static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
return size * nmemb;
}

错误码​

统一格式

字段类型说明
codeint预留字段,用于以后追踪问题
errorstring错误码,代码逻辑判断时使用
error_descriptionstring错误描述信息,开发的时候用来帮助理解和解决发生的错误

错误响应

错误码详细描述
invalid_request请求缺少某个必需参数,包含一个不支持的参数或参数值,或者格式不正确
invalid_timeMAC Token 算法中,ts 时间不合法,应请求服务器时间重新构造
invalid_clientclient_id 参数无效
access_denied授权服务器拒绝请求 这个状态出现在拿着 token 请求用户资源时,如出现,客户端应退出本地的用户登录信息,引导用户重新登录
forbidden用户没有对当前动作的权限,引导重新身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交
not_found请求失败,请求所希望得到的资源未被在服务器上发现。在参数相同的情况下,不应该重复请求
server_error服务器出现异常情况 可稍等后重新尝试请求,但需有尝试上限,建议最多 3 次,如一直失败,则中断并告知用户
insufficient_scope移动端进行 TapTap 授权使用的授权范围与服务端调用的 OAuth 接口不匹配导致,例如:移动端授权采用 basic_info 权限而服务端调用 获取当前账户详细信息 API 时则会返回该异常

常见问题​

调用 TapTap OAuth 接口返回 access_denied 错误码。​

1、 检查签算是否正确

请使用 TapTap 官方文档提供的示例脚本,验证签名计算是否正确。如果签算不正确,可能导致认证失败。

2、 Access Token 过期或无效

每个 Access Token 最长有效期为 30 天。SDK 在每次启动时会自动刷新 Token,确保通过 GetCurrentTapAccount 获取的 Token 始终有效。若玩家 30 天内未登录或不活跃,本地可能无法获取新 Token,需要用户重新登录。

3、 玩家主动注销或取消授权

若玩家在 TapTap 客户端内注销账号,或在授权管理中解除授权,后续的请求将返回 access_denied 错误。服务器应在 Token 校验失败时合理处理 access_denied,并引导用户重新登录。

4、 最佳实践:避免缓存 Token

不要对 Access Token 进行本地缓存,因为 Token 可能会随时失效。每次需要使用时,通过 GetCurrentTapAccount 获取最新的有效 Token,避免用户不必要的频繁登录操作。

调用 TapTap OAuth 接口返回 insufficient_scope 错误码。​

insufficient_scope 错误通常是由于 移动端授权的权限范围 与 服务端调用的 OAuth API 所需权限 不匹配 导致的。

例如:移动端在进行 TapTap 授权登录时申请的是 basic_info 权限,但服务端调用 获取当前账户详细信息 API 需要更高级别的权限,由于权限不足导致服务器返回该错误码。

权限匹配规则

通过合理配置 OAuth 授权范围,可有效避免 insufficient_scope 错误,确保 API 调用的顺畅性。