基于 OAuth2 与淘宝开放平台 API 的安全授权与数据同步机制设计
大家好,我是 微赚淘客系统3.0 的研发者省赚客!
在淘客系统中,获取用户授权以调用淘宝联盟(Taobao Union)API 是实现订单跟踪、佣金计算等核心功能的前提。淘宝开放平台采用 OAuth2.0 授权框架,我们基于此构建了一套高安全、可扩展的授权管理与数据同步机制,涵盖授权流程、Token 安全存储、自动刷新及异常处理。
一、OAuth2 授权流程集成
用户通过"绑定淘宝账号"入口跳转至淘宝授权页,完成授权后回调至系统。整个流程严格遵循 OAuth2 的 Authorization Code 模式:
- 构造授权 URL(含 app_key、redirect_uri、state)
- 用户同意授权,淘宝重定向并携带 code
- 后端用 code + app_secret 换取 access_token 和 refresh_token
java
package juwatech.cn.oauth.taobao;
import org.springframework.web.util.UriComponentsBuilder;
public class TaobaoAuthUrlBuilder {
private static final String AUTH_URL = "https://oauth.taobao.com/authorize";
public static String buildAuthUrl(String appId, String redirectUri, String state) {
return UriComponentsBuilder.fromHttpUrl(AUTH_URL)
.queryParam("response_type", "code")
.queryParam("client_id", appId)
.queryParam("redirect_uri", redirectUri)
.queryParam("state", state)
.queryParam("view", "web")
.toUriString();
}
}
回调接口处理 code 并换取 Token:
java
@GetMapping("/callback/taobao")
public ResponseEntity<?> handleCallback(@RequestParam String code, @RequestParam String state) {
// 校验 state 防 CSRF
juwatech.cn.security.StateValidator.validate(state);
TaobaoTokenResponse tokenResp = taobaoApiClient.getAccessToken(code);
// 绑定到当前用户
juwatech.cn.user.service.UserBindingService.bindTaobao(
getCurrentUserId(),
tokenResp.getAccessToken(),
tokenResp.getRefreshToken(),
tokenResp.getExpiresIn()
);
return ResponseEntity.ok().build();
}
二、Token 安全存储与加密
access_token 和 refresh_token 属于敏感凭证,禁止明文存储。我们在数据库中使用 AES 加密,并通过配置中心管理密钥:
java
package juwatech.cn.oauth.crypto;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
@Component
public class TokenEncryptor {
private final SecretKeySpec keySpec;
public TokenEncryptor(@Value("${token.encrypt.key}") String base64Key) {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
this.keySpec = new SecretKeySpec(keyBytes, "AES");
}
public String encrypt(String plain) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return Base64.getEncoder().encodeToString(cipher.doFinal(plain.getBytes()));
}
public String decrypt(String encrypted) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(encrypted);
return new String(cipher.doFinal(decoded));
}
}
用户绑定信息表结构如下:
sql
CREATE TABLE user_taobao_binding (
user_id BIGINT PRIMARY KEY,
encrypted_access_token VARCHAR(512) NOT NULL,
encrypted_refresh_token VARCHAR(512) NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
三、Token 自动刷新机制
淘宝 access_token 有效期为 12 小时,refresh_token 为 30 天。我们通过定时任务提前刷新即将过期的 Token:
java
@Scheduled(fixedRate = 300_000) // 每5分钟扫描
public void refreshExpiringTokens() {
List<UserBinding> expiring = bindingMapper.selectExpiringWithin(1, TimeUnit.HOURS);
for (UserBinding binding : expiring) {
try {
String decryptedRefreshToken = tokenEncryptor.decrypt(binding.getEncryptedRefreshToken());
TaobaoTokenResponse newToken = taobaoApiClient.refreshAccessToken(decryptedRefreshToken);
bindingMapper.updateTokens(
binding.getUserId(),
tokenEncryptor.encrypt(newToken.getAccessToken()),
tokenEncryptor.encrypt(newToken.getRefreshToken()),
System.currentTimeMillis() + newToken.getExpiresIn() * 1000
);
} catch (Exception e) {
// 刷新失败:记录日志,触发解绑或通知用户重新授权
juwatech.cn.monitor.AlertService.sendAlert("Token refresh failed for user: " + binding.getUserId());
}
}
}
四、API 调用封装与熔断降级
所有淘宝 API 调用通过统一客户端封装,集成签名、重试、熔断:
java
package juwatech.cn.taobao.client;
@Service
public class TaobaoApiClient {
private final RestTemplate restTemplate;
private final CircuitBreaker circuitBreaker;
public TaobaoOrderListResponse getOrderList(String accessToken, Date startTime) {
return circuitBreaker.executeSupplier(() -> {
String url = buildSignedUrl("taobao.tbk.order.details.get", accessToken,
Map.of("start_time", startTime.toString(), "page_no", "1"));
return restTemplate.getForObject(url, TaobaoOrderListResponse.class);
});
}
private String buildSignedUrl(String method, String accessToken, Map<String, String> params) {
Map<String, String> allParams = new HashMap<>();
allParams.put("method", method);
allParams.put("app_key", appId);
allParams.put("sign_method", "hmac");
allParams.put("timestamp", DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
allParams.put("v", "2.0");
allParams.put("access_token", accessToken);
allParams.putAll(params);
String sign = HmacUtils.hmacSha256(appSecret, buildQueryString(allParams));
return TAOPAI_API_URL + "?" + buildQueryString(allParams) + "&sign=" + sign;
}
}
配合 Resilience4j 实现熔断,避免因淘宝 API 限流导致服务雪崩。
五、数据同步与幂等处理
订单数据通过定时拉取(每10分钟)同步至本地,用于返利计算。为防止重复处理,采用唯一索引 + 幂等写入:
java
@Transactional
public void syncOrders(String userId) {
UserBinding binding = bindingService.getByUserId(userId);
if (binding == null || isTokenExpired(binding)) return;
List<TaobaoOrder> orders = taobaoApiClient.getOrderList(
decryptToken(binding.getEncryptedAccessToken()),
getLastSyncTime(userId)
);
for (TaobaoOrder order : orders) {
try {
// 唯一索引:trade_id + sub_trade_id
orderMapper.insertIgnoreDuplicate(order);
} catch (DuplicateKeyException ignored) {
// 已存在,跳过
}
}
syncLogMapper.updateLastSyncTime(userId, System.currentTimeMillis());
}
六、安全审计与权限最小化
- 所有授权操作记录审计日志(user_id, ip, timestamp)
- 应用仅申请必要 API 权限(如只读订单,不申请用户隐私)
- refresh_token 一旦使用即失效(淘宝支持),确保安全性
本文著作权归 微赚淘客系统3.0 研发团队,转载请注明出处!