tiktok第三方登录授权流程
需要先申请账号 拿到
tiktok:
oauth:
client-key: xxxx
client-secret: xxx
redirect-uri: xxx
这些参数从这里拿到 https://developers.tiktok.com/app/7584277019718944779/pending 审核需要时间,可以先用沙箱环境进行测试。
主要流程如下:
前端
↓ 跳转 TikTok 授权页
TikTok
↓ 用户确认授权
前端回调页
↓ 拿到 code
后端
↓ code 换 access_token
↓ access_token 拉用户信息
↓ 本地登录 / 注册
贴一下测试的h5页面
总结就是后端服务只需要提供一个回调接口 ,tiktok会回调给服务器code和state参数,code就是用来换取tiktok登录的token的,拿到token后就可以调用tiktok第三方用户详情接口了,拿到第三方用户详情后,就可以进行自己app的登录注册逻辑了
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>TikTok 登录测试</title>
<style>
body { font-family: Arial, sans-serif; margin: 50px; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
#userinfo { margin-top: 20px; }
img { width: 100px; height: 100px; border-radius: 50%; }
pre { background: #f5f5f5; padding: 10px; border-radius: 5px; }
</style>
</head>
<body>
<h1>TikTok 登录测试</h1>
<button id="loginBtn">TikTok 登录</button>
<div id="userinfo"></div>
<script>
const clientKey = "xxx"; // 替换成你的 TikTok client_key
const redirectUri = encodeURIComponent("xxx"); // 后端服务回调地址
const state = "12345"; // 随机值防 CSRF
document.getElementById("loginBtn").onclick = function() {
const url = `https://www.tiktok.com/v2/auth/authorize?client_key=${clientKey}&scope=user.info.basic&response_type=code&redirect_uri=${redirectUri}&state=${state}`;
window.location.href = url;
}
// 解析 URL 参数
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// 如果页面带有 code 和 state,则说明回调返回了
window.onload = function() {
const code = getQueryParam("code");
const returnedState = getQueryParam("state");
if(code) {
// 调用后端接口获取用户信息
fetch(`/tiktok/callback?code=${code}&state=${returnedState}`)
.then(res => res.json())
.then(data => {
const userDiv = document.getElementById("userinfo");
if(data.data) {
const user = data.data;
userDiv.innerHTML = `
<h3>用户信息</h3>
<img src="${user.avatar_url}" alt="avatar"/>
<p>用户信息: ${user}</p>
<p>昵称: ${user.display_name}</p>
<p>open_id: ${user.open_id}</p>
<pre>${JSON.stringify(user, null, 2)}</pre>
`;
} else {
userDiv.innerHTML = `<p>获取用户信息失败</p>`;
}
})
.catch(err => {
console.error(err);
document.getElementById("userinfo").innerHTML = "<p>接口请求错误</p>";
});
}
}
</script>
</body>
</html>
后端代码
@GetMapping("/auth/tiktok/callback") public R<AuthSuccessResult> loginTiktok(CommonHeader header, @RequestParam String code, @RequestParam String state) { // 这里的 "code" 来自 URL 查询参数 Assert.hasLength(code, "authCode cannot be null or empty"); try { String tokenUrl = "https://open.tiktokapis.com/v2/oauth/token/"; HttpResponse tokenResponse = HttpRequest.post(tokenUrl) .header("Content-Type", "application/x-www-form-urlencoded") .header("Cache-Control", "no-cache") .form("client_key", tiktokOAuthProperties.getClientKey()) .form("client_secret", tiktokOAuthProperties.getClientSecret()) .form("code", code) .form("grant_type", "authorization_code") .form("redirect_uri", tiktokOAuthProperties.getRedirectUri()) .execute(); //{ // "access_token": "act.example12345Example12345Example", // "expires_in": 86400, // "open_id": "asdf-12345c-1a2s3d-ac98-asdf123as12as34", // "refresh_expires_in": 31536000, // "refresh_token": "rft.example12345Example12345Example", // "scope": "user.info.basic,video.list", // "token_type": "Bearer" //} //{ // "error": "invalid_request", // "error_description": "The request parameters are malformed.", // "log_id": "202206221854370101130062072500FFA2" //} JSONObject tokenRes = JSONUtil.parseObj(tokenResponse.body()); log.info("TikTok换取access_token响应: {}", tokenRes); if (tokenRes.get("access_token") == null) { log.error("TikTok 换取 access_token 失败, 响应: {}", tokenRes); return R.status(STATUS_ERROR_AUTH, "tiktok auth failed"); } String accessToken = tokenRes.getStr("access_token"); String openId = tokenRes.getStr("open_id"); if (StringUtils.isBlank(openId)) { return R.status(STATUS_ERROR_AUTH, "tiktok open_id empty"); } // 2. 使用 access_token 获取用户信息 String userInfoUrl = "https://open.tiktokapis.com/v2/user/info/"; // 指定要获取的字段,可按需增删 String fields = "open_id,union_id,avatar_url,display_name"; // 发送 GET 请求 HttpResponse response = HttpRequest.get(userInfoUrl + "?fields=" + fields) .header("Authorization", "Bearer " + accessToken) .execute(); // 解析返回 JSON String userInfoBody = response.body(); JSONObject userInfoJson = JSONUtil.parseObj(userInfoBody); //{ // "data":{ // "user":{ // "avatar_url":"https://p19-sign.tiktokcdn-us.com/tos-avt-0068-tx/b17f0e4b3a4f4a50993cf72cda8b88b8~c5_168x168.jpeg", // "open_id":"723f24d7-e717-40f8-a2b6-cb8464cd23b4", // "union_id":"c9c60f44-a68e-4f5d-84dd-ce22faeb0ba1" // } // }, // "error":{ // "code":"ok", // "message":"", // "log_id":"20220829194722CBE87ED59D524E727021" // } //} log.info("TikTok获取用户信息响应: {}", userInfoJson); JSONObject userInfoData = userInfoJson.getJSONObject("data"); // 获取 user JSONObject userObj = userInfoData.getJSONObject("user"); //TikTok 第三方接口获取用户信息: user= // {display_name=xxxx, open_id=xxx, // union_id=c39504f0-e2a4-53a9-9b1e-693004c64c57, // avatar_url=https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7b7fe5d30c056d5b54bd055dbc1b3b54~tplv-tiktokx-cropcenter:168:168.jpeg?dr=9638&refresh_token=9f0c3ad0&x-expires=1766134800&x-signature=reknXEW77ReXn3TNPDpozvAVOL8%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=8aecc5ac&idc=useast5} log.info("TikTok 第三方接口获取用户信息: user={}", userObj); String avatarUrl = userObj.getStr("avatar_url"); String unionId = userObj.getStr("union_id"); String displayName = userObj.getStr("display_name"); // 3. 走统一的第三方登录流程 ThirdParam thirdParam = new ThirdParam(); thirdParam.setThirdId(openId); thirdParam.setThirdType(ThirdLoginEnum.TIKTOK.getDataFlag()); thirdParam.setNickname(displayName); // unionId 暂不入库,如需可扩展 center 服务 return R.ok(authService.loginThird(header, thirdParam)); } catch (Exception e) { log.error("TikTok 登录异常, authCode={},msg:{}", code, e.getMessage()); return R.status(STATUS_ERROR_AUTH, "tiktok auth exception"); } }
浙公网安备 33010602011771号