|
|
|
@ -31,32 +31,50 @@ public class TikTokAuthController { |
|
|
|
String state = generateState(); |
|
|
|
stateStore.put(state, System.currentTimeMillis()); |
|
|
|
String authUrl = authService.buildAuthUrl(state); |
|
|
|
// 生产环境建议 302 跳转:
|
|
|
|
// return "redirect:" + authUrl;
|
|
|
|
log.info("生成授权链接: {}", authUrl); |
|
|
|
return "请访问以下链接完成授权:\n" + authUrl; |
|
|
|
} |
|
|
|
|
|
|
|
@SaIgnore |
|
|
|
@GetMapping("/callback") |
|
|
|
public String callback( |
|
|
|
@RequestParam(value = "code", required = false) String code, |
|
|
|
@RequestParam(value = "state", required = false) String state, |
|
|
|
@RequestParam(value = "error", required = false) String error) { |
|
|
|
public String callback(@RequestParam Map<String, String> params) { |
|
|
|
|
|
|
|
// ★ 第一步:打印全部参数,不再盲目判断
|
|
|
|
log.info("===== TikTok 回调全部参数: {} =====", params); |
|
|
|
|
|
|
|
String code = params.get("code"); |
|
|
|
String state = params.get("state"); |
|
|
|
String error = params.get("error"); |
|
|
|
String shopRegion = params.get("shop_region"); |
|
|
|
|
|
|
|
// 1. 用户拒绝
|
|
|
|
if ("auth_denied".equals(error) || code == null || "null".equals(code)) { |
|
|
|
if ("auth_denied".equals(error)) { |
|
|
|
log.warn("用户拒绝授权, error={}", error); |
|
|
|
return "用户拒绝了授权"; |
|
|
|
} |
|
|
|
|
|
|
|
// 2. state 校验
|
|
|
|
if (state == null || !stateStore.containsKey(state)) { |
|
|
|
return "state 校验失败,可能存在 CSRF 攻击"; |
|
|
|
// 2. code 为空或字符串 "null"
|
|
|
|
if (code == null || "null".equals(code)) { |
|
|
|
log.warn("code 无效: code=[{}]", code); |
|
|
|
return "授权失败:未收到有效的授权码"; |
|
|
|
} |
|
|
|
|
|
|
|
// 3. state 校验(TikTok Shop 回调不一定带 state,放宽处理)
|
|
|
|
if (state != null) { |
|
|
|
if (!stateStore.containsKey(state)) { |
|
|
|
log.warn("state 校验失败: state={}", state); |
|
|
|
return "state 校验失败"; |
|
|
|
} |
|
|
|
stateStore.remove(state); |
|
|
|
log.info("state 校验通过: {}", state); |
|
|
|
} else { |
|
|
|
log.info("TikTok 未返回 state,跳过 state 校验"); |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 用 code 换 token
|
|
|
|
log.info("收到授权回调, code={}, state={}", code, state); |
|
|
|
// 4. 用 code 换 token
|
|
|
|
log.info("开始用 code 换取 token, code={}, shop_region={}", code, shopRegion); |
|
|
|
TikTokTokenResponse tokenResp = authService.getAccessToken(code); |
|
|
|
log.info("Token 接口响应: code={}, message={}", tokenResp.getCode(), tokenResp.getMessage()); |
|
|
|
|
|
|
|
if (tokenResp.getCode() != 0) { |
|
|
|
return "获取 Token 失败:" + tokenResp.getMessage(); |
|
|
|
@ -64,8 +82,8 @@ public class TikTokAuthController { |
|
|
|
|
|
|
|
TikTokTokenResponse.TokenData data = tokenResp.getData(); |
|
|
|
|
|
|
|
// 4. ⚠️ 生产环境应将 data 存入数据库/Redis,而非直接返回
|
|
|
|
return "授权成功!\n" |
|
|
|
+ "shop_region: " + shopRegion + "\n" |
|
|
|
+ "卖家名称: " + data.getSellerName() + "\n" |
|
|
|
+ "地区: " + data.getSellerBaseRegion() + "\n" |
|
|
|
+ "open_id: " + data.getOpenId() + "\n" |
|
|
|
|