当前位置: 首页 > news >正文

领券公众号 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 研发团队,转载请注明出处!

http://www.jsqmd.com/news/322710/

相关文章:

  • 搭建查券公众号后台:微信 XML 消息加解密与 AES 容错机制深度踩坑记录
  • 返利公众号 JSSDK 安全签名:JS-SDK ticket 缓存雪崩与容灾切换方案
  • 【毕业设计】基于SpringBoot的电脑维修工单管理系统的设计与实现(源码+文档+远程调试,全bao定制等)
  • 告别熬夜做PPT!8款傻瓜式生成工具,教师_职场人闭眼入
  • 完整教程:WPS润色AI半成品
  • 在java程序中使用其它接口获取数据
  • 告别熬夜赶PPT!学生党必备高效PPT生成工具推荐,效率直接翻倍
  • 2026年办公室布艺吸音板选购指南:TOP5实力厂家推荐+降噪效果实测对比
  • 在 Linux 中查看磁盘运行占用(I/O 使用率)
  • 深入解析:AI帮写JD实践指南:Spring Boot中集成SseEmitter实现流式输出
  • 中英文按视觉长度分割
  • C# 泛型编译后究竟长啥样?
  • 目标检测数据集 - 饮用水垃圾检测数据集下载
  • 为啥“泛型”非得在编译期把类型参数定死?——大白话讲透 C# 泛型背后的规矩(含很多生活比喻)
  • 1月30号
  • 反射调用为何疯狂GC?揭秘装箱与锯齿图
  • 文件在模型服务化中的各个状态IncomingFile➡FileItem;项目异常抛出体系;环境变量url与普通常量url区别;
  • 中英文、中英标点及数字按视觉长度分割
  • 2026简单易用的PPT智能生成工具及实操指南
  • 揭秘电商企业降本60%的SQL优化黄金法则
  • 超轻量图片水印添加工具:13.5KB绿色版,支持自定义内容与位置
  • 告别熬夜做PPT!5款高性价比AI生成工具,效率翻倍不踩坑
  • 考执业医师哪个课程好?小编推荐你选阿虎医考!
  • 爆了!关于2026开年3位程序员接连猝死事件对普通人的启示录一
  • 视频批量智能分割工具:一键自动剪辑与镜头识别教程
  • 考中医执业医师,到底哪个老师讲得好?
  • 告别熬夜做PPT!3款AI一键生成神器,学生党职场人闭眼冲
  • 备课效率翻倍!2026教师专用PPT工具全攻略:传统神器+AI黑科技一网打尽
  • 告别PPT排版焦虑!4个宝藏模板平台,覆盖全场景需求
  • 告别熬夜做PPT!4款宝藏生成工具实测,小白也能秒变设计大神