跳到主要内容
版本: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 id 和 Client 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_token、mac_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