跳到主要内容
版本:v4

TapTap OAuth 接口

概述

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

基本概念

  • Access Token:用户通过 SDK 完成 TapTap 登录授权后,SDK 返回的凭证对象,包含 kidmac_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 签算

签算所需的 kidmac_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 调用的顺畅性。