获取登录信息
概述
OpenAPI 采用统一的 Mac Token 头部签算来传递用户身份。
接入客户端 SDK 后,经过用户的授权流程,会获得这个用户在当前应用中的 Mac Token。Mac Token 长期有效,只有在用户更新自己账号相关安全信息、注销对当前应用的授权时才会失效。开发者应当将 Mac Token 妥善保管于自己的服务器上,作为后续与 TapTap 服务端通讯的标示。(Mac Token 算法细节见文档中的 MAC Token 算法 部分)
以下接口,均提供为国内示例,海外用户请参考海外 API 说明。
流程
-
移动端用 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; -
再把移动端获取的参数发到游戏务服务器,服务端签算 mac token。
-
请求
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_id | string | 该应用的 Client ID ,应与约定相同 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
user_id | string | tds id,用户唯一标识 |
name | string | 用户名 |
avatar | string | 用户头像图片 |
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 包含以下字段:
字段 | 类型 | 说明 |
---|---|---|
kid | string | mac_key id, The key identifier. |
access_token | string | 该字段暂无作用 |
token_type | string | Token 类型,如 mac |
mac_key | string | mac 密钥 |
mac_algorithm | string | mac 计算的算法名称 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 + "\"";
}
}
通用接口错误信息
统一格式
字段 | 类型 | 说明 |
---|---|---|
code | int | 预留字段,用于以后追踪问题 |
error | string | 错误码,代码逻辑判断时使用 |
error_description | string | 错误描述信息,开发的时候用来帮助理解和解决发生的错误 |
错误响应
错误码 | 详细描述 |
---|---|
invalid_request | 请求缺少某个必需参数,包含一个不支持的参数或参数值,或者格式不正确 |
invalid_time | MAC Token 算法中,ts 时间不合法,应请求服务器时间重新构造 |
invalid_client | client_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