news 2026/7/22 18:06:32

领券公众号 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/7/1 20:57:56

一库平替,融合致胜:国产数据库的“统型”范式革命

在数字化转型步入深水区的今天,我们正见证一场发生在数据基础设施底层的静默革命。当企业面对Oracle、MySQL、SQL Server、GIS数据库、时序数据库、文档数据库乃至向量数据库组成的复杂“数据库动物园”时,技术栈的割裂、成本的飙升与数据孤岛的固化已成…

作者头像 李华
网站建设 2026/7/21 21:26:44

GitExtension下载、安装

一、GitExtension下载 官网下载 根据自己环境选择,我这里选择的x64 二、安装 三、配置 或者

作者头像 李华
网站建设 2026/7/21 17:39:54

【阿里云】—— 云服务器 ECS搭建与使用

阿里云ECS主机从零配置指南:新手友好,避坑必看(含完整实操) 对于新手开发者、个人站长或初级运维来说,阿里云ECS主机的配置绝对是入门路上的“第一道坎”——买了ECS不知道怎么登录、初始化配置混乱、安全组没开导致无…

作者头像 李华
网站建设 2026/7/21 20:15:01

26.4 范围管理:案例分析理论题必背要点

一、范围管理过程中存在的问题(一)规划范围管理问题现象专业表述正确做法💡 解析与示例未编制范围管理计划和需求管理计划缺少正式文件指导需求与范围管理工作应制定详细的范围管理计划与需求管理计划✅ 计划是“导航图”,无计划即…

作者头像 李华
网站建设 2026/7/5 12:19:16

ue 预览衣服

目录 预览方法2 测试ok 我把shift 的material 拖动到body的Skeletal Mesh,中,没看见衣服 预览方法2 测试ok body的Skeletal Mesh打开, 选择pelvis,asset details中,选择了 texture-> shift mat, 效果1: 清空衣服…

作者头像 李华