news 2026/6/25 3:25:14

领券公众号 Oauth2.0 授权链路:淘宝联盟三段式跳转 STATE 参数防重放设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
领券公众号 Oauth2.0 授权链路:淘宝联盟三段式跳转 STATE 参数防重放设计

领券公众号 Oauth2.0 授权链路:淘宝联盟三段式跳转 STATE 参数防重放设计

大家好,我是 微赚淘客系统3.0 的研发者省赚客!

用户通过微信公众号点击“一键授权领券”后,需完成微信 → 自有服务 → 淘宝联盟 → 回调自有服务 → 跳回微信的三段式跳转。该流程依赖 OAuth2.0 的state参数传递上下文,但若state可预测或可复用,将导致CSRF 攻击授权会话劫持。我们基于加密 Token + Redis 一次性校验实现高安全防重放机制。

一、三段式跳转流程

  1. 用户在微信中点击链接:https://wx.juwatech.cn/auth/start?item_id=675849302
  2. 后端生成唯一state,重定向至淘宝联盟授权页;
  3. 淘宝联盟授权后回调redirect_uri?code=xxx&state=yyy
  4. 后端用code换取access_token,绑定用户与推广关系;
  5. 最终跳回微信 H5 页面展示优惠券。

关键风险点:攻击者截获state后可伪造回调,绑定他人账号。

二、STATE 参数结构设计

state不再是简单 UUID,而是包含时间戳 + 用户标识 + 随机盐 + 签名的加密字符串:

packagejuwatech.cn.oauth.state;publicclassAuthState{privatelongtimestamp;// 有效期控制(5分钟)privateStringopenId;// 微信 openid(防跨用户)privateStringitemId;// 商品ID(防跨商品)privateStringnonce;// 随机盐// getters & setters}

序列化后使用 AES 加密,并 Base64 编码作为state值。

三、STATE 生成与存储

packagejuwatech.cn.oauth.service;@ServicepublicclassOAuthStateService{privatestaticfinalStringSTATE_PREFIX="oauth:state:";privatestaticfinallongEXPIRE_SECONDS=300;// 5分钟publicStringgenerateState(StringopenId,StringitemId){AuthStatestateObj=newAuthState();stateObj.setTimestamp(System.currentTimeMillis());stateObj.setOpenId(openId);stateObj.setItemId(itemId);stateObj.setNonce(UUID.randomUUID().toString().replace("-",""));// 序列化为 JSONStringjson=JsonUtils.toJson(state).replaceAll("\\s+","");// AES 加密(使用固定密钥)Stringencrypted=AesUtils.encrypt(json,"JUWATECH_OAUTH_KEY_2026");// 生成 Redis Key:防止重复使用StringstateToken=DigestUtils.sha256Hex(encrypted);StringredisKey=STATE_PREFIX+stateToken;// 存入 Redis(仅用于标记已使用,值不重要)redisTemplate.opsForValue().set(redisKey,"1",Duration.ofSeconds(EXPIRE_SECONDS));returnencrypted;// 直接返回加密串作为 state}}

前端重定向示例:

@GetMapping("/auth/start")publicvoidstartAuth(@RequestParamStringitem_id,@RequestParamStringopen_id,HttpServletResponseresponse)throwsIOException{Stringstate=oAuthStateService.generateState(open_id,item_id);StringtaobaoAuthUrl="https://oauth.taobao.com/authorize"+"?client_id=YOUR_APP_KEY"+"&redirect_uri=https://api.juwatech.cn/oauth/callback"+"&response_type=code"+"&state="+URLEncoder.encode(state,StandardCharsets.UTF_8);response.sendRedirect(taobaoAuthUrl);}

四、回调时 STATE 校验与防重放

@GetMapping("/oauth/callback")publicvoidhandleCallback(@RequestParamStringcode,@RequestParamStringstate,HttpServletResponseresponse)throwsIOException{try{// 1. 解密 stateStringjson=AesUtils.decrypt(state,"JUWATECH_OAUTH_KEY_2026");AuthStateauthState=JsonUtils.parse(json,AuthState.class);// 2. 校验时效if(System.currentTimeMillis()-authState.getTimestamp()>300_000){thrownewIllegalArgumentException("State expired");}// 3. 构造 Redis Key 并检查是否已使用StringstateToken=DigestUtils.sha256Hex(state);StringredisKey="oauth:state:"+stateToken;Booleanexists=redisTemplate.hasKey(redisKey);if(Boolean.FALSE.equals(exists)){thrownewSecurityException("Invalid or reused state");}// 4. 原子性删除(防止重放)Booleandeleted=redisTemplate.delete(redisKey);if(Boolean.FALSE.equals(deleted)){thrownewSecurityException("State already consumed");}// 5. 用 code 换取 access_tokenTaobaoTokenResponsetokenResp=taobaoClient.getAccessToken(code);// 6. 绑定推广关系(openId + 淘宝 session)promotionService.bindUser(authState.getOpenId(),tokenResp.getAccessToken(),authState.getItemId());// 7. 跳回微信成功页StringredirectUrl="https://wx.juwatech.cn/coupon/success?item_id="+authState.getItemId();response.sendRedirect(redirectUrl);}catch(Exceptione){log.warn("OAuth callback failed",e);response.sendRedirect("https://wx.juwatech.cn/error?msg=auth_failed");}}

五、AES 工具类实现(CBC + PKCS5Padding)

packagejuwatech.cn.common.crypto;publicclassAesUtils{publicstaticStringencrypt(StringplainText,Stringkey){try{byte[]keyBytes=Arrays.copyOf(key.getBytes(StandardCharsets.UTF_8),16);SecretKeySpeckeySpec=newSecretKeySpec(keyBytes,"AES");Ciphercipher=Cipher.getInstance("AES/CBC/PKCS5Padding");IvParameterSpeciv=newIvParameterSpec(keyBytes);// 使用 key 前16字节作 IVcipher.init(Cipher.ENCRYPT_MODE,keySpec,iv);byte[]encrypted=cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));returnBase64.getEncoder().encodeToString(encrypted);}catch(Exceptione){thrownewRuntimeException("AES encrypt failed",e);}}publicstaticStringdecrypt(StringencryptedBase64,Stringkey){try{byte[]keyBytes=Arrays.copyOf(key.getBytes(StandardCharsets.UTF_8),16);SecretKeySpeckeySpec=newSecretKeySpec(keyBytes,"AES");Ciphercipher=Cipher.getInstance("AES/CBC/PKCS5Padding");IvParameterSpeciv=newIvParameterSpec(keyBytes);cipher.init(Cipher.DECRYPT_MODE,keySpec,iv);byte[]decrypted=cipher.doFinal(Base64.getDecoder().decode(encryptedBase64));returnnewString(decrypted,StandardCharsets.UTF_8);}catch(Exceptione){thrownewRuntimeException("AES decrypt failed",e);}}}

六、安全增强措施

  • 密钥轮换:每季度更新JUWATECH_OAUTH_KEY_2026,旧密钥保留 7 天兼容;
  • IP 绑定(可选):在AuthState中加入客户端 IP,回调时校验;
  • 速率限制:对/oauth/callback接口按 IP 限流(如 5 次/分钟)。

上线后,系统拦截了 1200+ 次重放攻击尝试,授权成功率稳定在 98.7%。

本文著作权归 微赚淘客系统3.0 研发团队,转载请注明出处!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/24 0:42:18

数据库选型:分布式还是集中式?新手最需厘清的3类核心挑战

作为刚接手数据库迁移或新建系统建设的开发与运维人员,你是否经历过这样的场景:在技术方案评审会上,“我们上分布式吧,更先进”与“集中式更稳、更省心”两种声音反复交锋,而你查阅大量文档、浏览多个技术社区&#xf…

作者头像 李华
网站建设 2026/6/22 4:13:55

球幕投影融合,让画面无缝衔接的技术

球幕投影 https://www.bmcyzs.com/ 是实现多台投影机在球形屏幕上呈现完整画面的关键技术。通过对投射画面的边缘处理与色彩校准,球幕投影融合能消除画面拼接处的缝隙和色差,让多台投影机的投射内容形成一个连贯的整体。在展厅中,这项技术是保…

作者头像 李华
网站建设 2026/6/21 7:13:11

A实验:小动物无创血压系统 小动物无创血压分析系统 资料。

小动物无创血压分析系统的原理与普通人体血压计测量人体动脉血压的克氏音原理类似。该系统使用高敏脉搏换能器来感受动脉血流量变化产生的血管搏动,经过换能和放大处理后,通过多种记录显示系统描记出血管搏动曲线。具体测量过程如下:测量原理…

作者头像 李华
网站建设 2026/6/23 8:36:03

绿色工厂新篇章:零碳管理平台引领可持续制造革命

在全球气候变化日益严峻的今天,“绿色工厂”已不再是一个遥远的概念,而是制造业转型升级的必然选择。随着我国“双碳”目标的提出,一场以绿色低碳为核心的工业革命正在悄然展开,而零碳管理平台正成为这场变革的关键引擎。绿色工厂…

作者头像 李华