跳到主要内容
版本:v2

获取登录信息

概述

OpenAPI 采用统一的 Mac Token 头部签算来传递用户身份。

接入客户端 SDK 后,经过用户的授权流程,会获得这个用户在当前应用中的 Mac Token。Mac Token 长期有效,只有在用户更新自己账号相关安全信息、注销对当前应用的授权时才会失效。开发者应当将 Mac Token 妥善保管于自己的服务器上,作为后续与 TapTap 服务端通讯的标示。(Mac Token 算法细节见文档中的 MAC Token 算法 部分)

以下接口,均提供为国内示例,海外用户请参考海外 API 说明

流程

  1. 移动端用 SDK 的 TapTap 登录,可以通过 GetAccessToken 获取 AccessToken,里面包含

    public String kid;
    public String access_token;
    public String token_type;
    public String mac_key;
    public String mac_algorithm;
    public String expire_in;
    private String json = null;
  2. 再把移动端获取的参数发到游戏务服务器,服务端签算 mac token。

  3. 请求 https://tds-tapsdk.cn.tapapis.com/api/v1/user/info , header 携带 mac token

注意:当前实际返回的 kid 和 access_token 值相等,建议使用 access_token

API

获取当前账户详细信息

GET https://tds-tapsdk.cn.tapapis.com/api/v1/user/info?client_id=xxx
Authorization mac token

请求参数

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

响应参数

字段类型说明
user_idstringtds id,用户唯一标识
namestring用户名
avatarstring用户头像图片
gender  int  UNKNOWN = 0;
MALE = 1;
FEMALE = 2
is_guest  bool  是否是游客,暂时弃用

请求示例

替换其中的 MAC idClient ID 为自己签算的 mac token 和控制台的 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://tds-tapsdk.cn.tapapis.com/api/v1/user/info?client_id=<Client ID>"

其他

MAC Token 算法

MAC Token 包含以下字段:

字段类型说明
kidstringmac_key id, The key identifier.
access_tokenstring该字段暂无作用
token_typestringToken 类型,如 mac
mac_keystringmac 密钥
mac_algorithmstringmac 计算的算法名称 hmac-sha-1

使用 Mac Token 签算一个接口:

脚本请求示例

可用此脚本验证直接替换参数,用来验证自己服务端签算的 mac token 是否正确
CLIENT_ID 替换为控制台获取的 Client ID,ACCESS_TOKEN 和 MAC_KEY 为客户端登录成功后的 access_tokenmac_key

#!/usr/bin/env bash

# 客户端 ID
CLIENT_ID="请替换为控制台的 `Client ID`"
# SDK 获取的 access_token
ACCESS_TOKEN="1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ"
# SDK 获取的 mac_key
MAC_KEY="mSUQNYUGRBPXyRyW"

# 随机数,正式上线请替换
NONCE="abcdef"
# 当前时间戳
TS=$(date +%s)

# 请求方法
METHOD="GET"
# 请求地址 (带 query string)
REQUEST_URI="/api/v1/user/info?client_id=${CLIENT_ID}"
# 请求域名
REQUEST_HOST="tds-tapsdk.cn.tapapis.com"

MAC=$(printf "%s\n%s\n%s\n%s\n%s\n443\n\n" "${TS}" "${NONCE}" "${METHOD}" "${REQUEST_URI}" "${REQUEST_HOST}" | openssl dgst -binary -sha1 -hmac ${MAC_KEY} | base64)

AUTHORIZATION=$(printf 'MAC id="%s",ts="%s",nonce="%s",mac="%s"' "${ACCESS_TOKEN}" "${TS}" "${NONCE}" "${MAC}")

curl -s -H"Authorization:${AUTHORIZATION}" "https://tds-tapsdk.cn.tapapis.com/api/v1/user/info?client_id=${CLIENT_ID}"

nodejs 请求示例

const urllib = require('urllib');
var format = require('string-format');
const utils = require('./utils');
/**
TapSDK 登录后信息获取
**/
var kid = "1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ";
var mac_key = "mSUQNYUGRBPXyRyW";
var nonce = "adssd";
var client_id = "0RiAlMny7jiz086FaU";


var ts = Math.ceil(Date.now() / 1000);
var ext = "";
var signArray = [ts, nonce, 'GET', '/api/v1/user/info?client_id=' + client_id, 'tds-tapsdk.cn.tapapis.com', 443, ext];

var mac = utils.hmacSha1(signArray.join("\n")+"\n", mac_key);
var auth = format('MAC id={id},ts={ts},nonce={nonce},mac={mac}', {
id: '\"'+kid+'\"',
ts: '\"'+ts+'\"',
nonce: '\"'+nonce+'\"',
mac: '\"'+mac+'\"'
});

var headers = {
Authorization: auth
}

var reqData = {
method: "GET",
headers: headers
}

urllib.request("https://tds-tapsdk.cn.tapapis.com/api/v1/user/info?client_id=" + client_id, reqData,
(err, data, response) => {
if(!err){
console.log("返回数据:" + data.toString());
}
});

//utils
var crypto = require('crypto');

exports.base64ToUrlSafe = function (v) {
return v.replace(/\//g, '_').replace(/\+/g, '-');
};

exports.urlsafeBase64Encode = function (jsonFlags) {
var encoded = Buffer.from(jsonFlags).toString('base64');
return exports.base64ToUrlSafe(encoded);
};

exports.hmacSha1 = function (encodedFlags, secretKey) {
var hmac = crypto.createHmac('sha1', secretKey);
hmac.update(encodedFlags);
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.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
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 = "0RiAlMny7jiz086FaU";
String kid = "1/hC0vtMo7ke0Hkd-iI8-zcAwy7vKds9si93l7qBmNFxJkylWEOYEzGqa7k_9iw_bb3vizf-3CHc6U8hs-5a74bMFzkkz7qC2HdifBEHsW9wxOBn4OsF9vz4Cc6CWijkomnOHdwt8Km6TywOX5cxyQv0fnQQ9fEHbptkIJagCd33eBXg76grKmKsIR-YUZd1oVHu0aZ6BR7tpYYsCLl-LM6ilf8LZpahxQ28n2c-y33d-20YRY5NW1SnR7BorFbd00ZP97N9kwDncoM1GvSZ7n90_0ZWj4a12x1rfAWLuKEimw1oMGl574L0wE5mGoshPa-CYASaQmBDo3Q69XbjTsKQ"; // kid
String mac_key = "mSUQNYUGRBPXyRyW"; // mac_key
String method = "GET";
String request_url = "https://tds-tapsdk.cn.tapapis.com/api/v1/user/info?client_id=" + client_id; //
String authorization = getAuthorization(request_url, method, kid, mac_key);
System.out.println(authorization);
URL url = new URL(request_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Http
conn.setRequestProperty("Authorization", authorization);
conn.setRequestMethod("GET");
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 request_url
* @param method "GET" or "POST"
* @param key_id key id by OAuth 2.0
* @param mac_key mac key by OAuth 2.0
* @return authorization string
*/
public static String getAuthorization(String request_url, String method, String key_id, String
mac_key) {
try {
URL url = new URL(request_url);
String time = String.format(Locale.US, "%010d", System.currentTimeMillis() / 1000);
String randomStr = getRandomString(5);
String host = url.getHost();
String uri = request_url.substring(request_url.lastIndexOf(host) + host.length());
String port = "80";
if (request_url.startsWith("https")) {
port = "443";
}
String other = "";
String sign = sign(mergeSign(time, randomStr, method, uri, host, port, other), mac_key);
return "MAC " + getAuthorizationParam("id", key_id) + "," + getAuthorizationParam("ts", time)
+ "," + getAuthorizationParam("nonce", randomStr) + "," + getAuthorizationParam("mac",
sign);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
private static String getRandomString(int length) { //length
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
private static String mergeSign(String time, String randomCode, String httpType, String uri,
String domain, String port, String other) {
if (time.isEmpty() || randomCode.isEmpty() || httpType.isEmpty() || domain.isEmpty() || port.isEmpty())
{
return null;
}
String prefix =
time + "\n" + randomCode + "\n" + httpType + "\n" + uri + "\n" + domain + "\n" + port
+ "\n";
if (other.isEmpty()) {
prefix += "\n";
} else {
prefix += (other + "\n");
}
return prefix;
}
private static String sign(String signatureBaseString, String key) {
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] text = signatureBaseString.getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = mac.doFinal(text);
signatureBytes = Base64.getEncoder().encode(signatureBytes);
return new String(signatureBytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException(e);
}
}
private static String getAuthorizationParam(String key, String value) {
if (key.isEmpty() || value.isEmpty()) {
return null;
}
return key + "=" + "\"" + value + "\"";
}
}

通用接口错误信息

统一格式

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

错误响应

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

海外 API 说明

当移动端初始化为海外时

TapConfig tapConfig = new TapConfig("your-client-id", false); // true 表示国内,false 表示国外
TapBootstrap.Init(tapConfig);

登录即为海外,服务端文档以上流程不变,变更海外域名即可

海外域名:

main domain
- host: tds-tapsdk0.intl.tapapis.com
- host: tds-tapsdk1.intl.tapapis.com
- host: tds-tapsdk2.intl.tapapis.com
backup domain
- host: tds-tapsdk-b0.intl.tapapis.com
- host: tds-tapsdk-b1.intl.tapapis.com