优惠券app安全策略:基于OAuth2.0的第三方平台授权与数据保护

优惠券app安全策略:基于OAuth2.0的第三方平台授权与数据保护

大家好,我是省赚客APP研发者阿可!省赚客APP(juwatech.cn)需对接淘宝联盟、京东联盟等第三方开放平台,以获取用户授权后的商品数据、订单信息及佣金明细。为保障用户隐私与平台安全,我们严格遵循 OAuth 2.0 协议实现授权流程,并在后端构建完整的 Token 管理、加密存储与访问控制机制。本文将结合 Java 实现代码,详解从授权跳转到 API 调用的全链路安全设计。

OAuth2.0 授权码模式集成

以淘宝联盟为例,采用标准 Authorization Code Flow:

  1. 用户点击"绑定淘宝账号";
  2. 跳转至 https://oauth.taobao.com/authorize?client_id=xxx&redirect_uri=https://api.juwatech.cn/oauth/callback/taobao&response_type=code
  3. 用户授权后,回调携带 code
  4. 后端用 code 换取 access_token

前端跳转逻辑(由 APP 内 WebView 触发):

java 复制代码
@GetMapping("/oauth/authorize/taobao")
public String authorizeTaobao() {
    String url = "https://oauth.taobao.com/authorize?" +
        "client_id=" + TAOTAO_APP_KEY +
        "&redirect_uri=" + URLEncoder.encode("https://api.juwatech.cn/oauth/callback/taobao", StandardCharsets.UTF_8) +
        "&response_type=code" +
        "&state=" + generateSecureState();
    return "redirect:" + url;
}

Token 获取与安全存储

回调接口接收 code 并请求 Token:

java 复制代码
@PostMapping("/oauth/callback/taobao")
public ResponseEntity<String> handleTaobaoCallback(@RequestParam String code,
                                                   @RequestParam String state,
                                                   @RequestHeader("X-User-ID") Long userId) {
    if (!isValidState(state)) {
        return ResponseEntity.status(400).body("Invalid state");
    }

    // 请求 access_token
    RestTemplate restTemplate = new RestTemplate();
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("grant_type", "authorization_code");
    params.add("code", code);
    params.add("client_id", TAOTAO_APP_KEY);
    params.add("client_secret", TAOTAO_APP_SECRET);
    params.add("redirect_uri", "https://api.juwatech.cn/oauth/callback/taobao");

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

    TaobaoTokenResponse response = restTemplate.postForObject(
        "https://oauth.taobao.com/token", request, TaobaoTokenResponse.class);

    // 加密存储(使用 AES-GCM)
    String encryptedRefreshToken = AesGcmUtil.encrypt(response.getRefreshToken(), ENCRYPTION_KEY);
    String encryptedAccessToken = AesGcmUtil.encrypt(response.getAccessToken(), ENCRYPTION_KEY);

    // 存入数据库,关联用户ID
    oauthTokenRepository.save(OauthToken.builder()
        .userId(userId)
        .platform("TAOBAO")
        .accessToken(encryptedAccessToken)
        .refreshToken(encryptedRefreshToken)
        .expiresIn(response.getExpiresIn())
        .createTime(System.currentTimeMillis())
        .build());

    return ResponseEntity.ok("绑定成功");
}

加密工具类:

java 复制代码
package juwatech.cn.security;

public class AesGcmUtil {
    private static final int GCM_IV_LENGTH = 12;
    private static final int GCM_TAG_LENGTH = 16;

    public static String encrypt(String plaintext, SecretKey key) {
        try {
            byte[] iv = new byte[GCM_IV_LENGTH];
            new SecureRandom().nextBytes(iv);
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, spec);
            byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(ByteBuffer.allocate(iv.length + ciphertext.length)
                .put(iv).put(ciphertext).array());
        } catch (Exception e) {
            throw new RuntimeException("Encryption failed", e);
        }
    }

    public static String decrypt(String encrypted, SecretKey key) {
        try {
            byte[] data = Base64.getDecoder().decode(encrypted);
            ByteBuffer buffer = ByteBuffer.wrap(data);
            byte[] iv = new byte[GCM_IV_LENGTH];
            buffer.get(iv);
            byte[] ciphertext = new byte[buffer.remaining()];
            buffer.get(ciphertext);
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("Decryption failed", e);
        }
    }
}

API 调用时自动刷新 Token

封装平台客户端,在调用前检查 Token 有效性:

java 复制代码
@Component
public class TaobaoApiClient {

    public TaobaoItem getItemInfo(String itemId, Long userId) {
        OauthToken tokenRecord = oauthTokenRepository.findByUserIdAndPlatform(userId, "TAOBAO");
        String accessToken = AesGcmUtil.decrypt(tokenRecord.getAccessToken(), ENCRYPTION_KEY);

        // 尝试调用
        try {
            return doCall(itemId, accessToken);
        } catch (InvalidTokenException e) {
            // 刷新 Token
            String newAccessToken = refreshToken(tokenRecord);
            return doCall(itemId, newAccessToken);
        }
    }

    private String refreshToken(OauthToken record) {
        String encryptedRefresh = record.getRefreshToken();
        String refreshToken = AesGcmUtil.decrypt(encryptedRefresh, ENCRYPTION_KEY);

        // 调用淘宝 refresh_token 接口
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "refresh_token");
        params.add("refresh_token", refreshToken);
        params.add("client_id", TAOTAO_APP_KEY);
        params.add("client_secret", TAOTAO_APP_SECRET);

        TaobaoTokenResponse resp = restTemplate.postForObject(
            "https://oauth.taobao.com/token", new HttpEntity<>(params), TaobaoTokenResponse.class);

        // 更新数据库
        String newEncryptedAccess = AesGcmUtil.encrypt(resp.getAccessToken(), ENCRYPTION_KEY);
        oauthTokenRepository.updateAccessToken(record.getId(), newEncryptedAccess, resp.getExpiresIn());

        return resp.getAccessToken();
    }
}

权限隔离与审计日志

所有涉及用户数据的接口强制校验 userId 与 Token 所属用户一致:

java 复制代码
@GetMapping("/user/orders")
public List<Order> getUserOrders(@RequestHeader("X-User-ID") Long currentUserId,
                                 @RequestParam String platform) {
    // 防止越权:确保当前用户已绑定该平台
    if (!oauthTokenRepository.existsByUserIdAndPlatform(currentUserId, platform)) {
        throw new AccessDeniedException("未授权访问");
    }
    return orderService.queryByUser(currentUserId, platform);
}

关键操作记录审计日志:

java 复制代码
@EventListener
public void onTokenRefresh(TokenRefreshedEvent event) {
    auditLogService.log(AuditLog.builder()
        .userId(event.getUserId())
        .action("REFRESH_TOKEN")
        .target("TAOBAO")
        .timestamp(System.currentTimeMillis())
        .build());
}

本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!

相关推荐
迷藏4943 分钟前
**发散创新:基于Rust实现的开源合规权限管理框架设计与实践**在现代软件架构中,**权限控制(RBAC)** 已成为保障
java·开发语言·python·rust·开源
wuxinyan1231 小时前
Java面试题47:一文深入了解Nginx
java·nginx·面试题
新知图书1 小时前
搭建Spring Boot开发环境
java·spring boot·后端
冰河团队1 小时前
一个拉胯的分库分表方案有多绝望?整个部门都在救火!
java·高并发·分布式数据库·分库分表·高性能
洛_尘1 小时前
Java EE进阶:Linux的基本使用
java·java-ee
宸津-代码粉碎机1 小时前
Spring Boot 4.0虚拟线程实战调优技巧,最大化发挥并发优势
java·人工智能·spring boot·后端·python
MaCa .BaKa1 小时前
47-心里健康咨询平台/心理咨询系统
java·spring boot·mysql·tomcat·maven·intellij-idea·个人开发
木子欢儿2 小时前
Docker Hub 镜像发布指南
java·spring cloud·docker·容器·eureka
Devin~Y2 小时前
高并发电商与AI智能客服场景下的Java面试实战:从Spring Boot到RAG与向量数据库落地
java·spring boot·redis·elasticsearch·spring cloud·kafka·rag
蜡台2 小时前
IDEA 一些 使用配置和插件
java·ide·intellij-idea