核心内容摘要
欲梦子在繁华都市中寻觅一方精神栖息地_2
领券公众号 Oauth
0 授权链路淘宝联盟三段式跳转 STATE 参数防重放设计大家好我是 微赚淘客系统
0 的研发者省赚客用户通过微信公众号点击“一键授权领券”后需完成微信 → 自有服务 → 淘宝联盟 → 回调自有服务 → 跳回微信的三段式跳转。
该流程依赖 OAuth
0 的state参数传递上下文但若state可预测或可复用将导致CSRF 攻击或授权会话劫持。
我们基于加密 Token Redis 一次性校验实现高安全防重放机制。
三段式跳转流程用户在微信中点击链接https://wx.juwatech.cn/auth/start?item_id675849302后端生成唯一state重定向至淘宝联盟授权页淘宝联盟授权后回调redirect_uri?codexxxstateyyy后端用code换取access_token绑定用户与推广关系最终跳回微信 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_PREFIXoauth:state:;privatestaticfinallongEXPIRE_SECONDS300;// 5分钟publicStringgenerateState(StringopenId,StringitemId){AuthStatestateObjnewAuthState();stateObj.setTimestamp(System.currentTimeMillis());stateObj.setOpenId(openId);stateObj.setItemId(itemId);stateObj.setNonce(UUID.randomUUID().toString().replace(-,));// 序列化为 JSONStringjsonJsonUtils.toJson(state).replaceAll(\\s,);// AES 加密使用固定密钥StringencryptedAesUtils.encrypt(json,JUWATECH_OAUTH_KEY_
;// 生成 Redis Key防止重复使用StringstateTokenDigestUtils.sha256Hex(encrypted);StringredisKeySTATE_PREFIXstateToken;// 存入 Redis仅用于标记已使用值不重要redisTemplate.opsForValue().set(redisKey,1,Duration.ofSeconds(EXPIRE_SECONDS));returnencrypted;// 直接返回加密串作为 state}}前端重定向示例GetMapping(/auth/start)publicvoidstartAuth(RequestParamStringitem_id,RequestParamStringopen_id,HttpServletResponseresponse)throwsIOException{StringstateoAuthStateService.generateState(open_id,item_id);StringtaobaoAuthUrlhttps://oauth.taobao.com/authorize?client_idYOUR_APP_KEYredirect_urihttps://api.juwatech.cn/oauth/callbackresponse_typecodestateURLEncoder.encode(state,StandardCharsets.UTF_
;response.sendRedirect(taobaoAuthUrl);}
回调时 STATE 校验与防重放GetMapping(/oauth/callback)publicvoidhandleCallback(RequestParamStringcode,RequestParamStringstate,HttpServletResponseresponse)throwsIOException{try{//
解密 stateStringjsonAesUtils.decrypt(state,JUWATECH_OAUTH_KEY_
;AuthStateauthStateJsonUtils.parse(json,AuthState.class);//
校验时效if(System.currentTimeMillis()-authState.getTimestamp()300_
{thrownewIllegalArgumentException(State expired);}//
构造 Redis Key 并检查是否已使用StringstateTokenDigestUtils.sha256Hex(state);StringredisKeyoauth:state:stateToken;BooleanexistsredisTemplate.hasKey(redisKey);if(Boolean.FALSE.equals(exists)){thrownewSecurityException(Invalid or reused state);}//
原子性删除防止重放BooleandeletedredisTemplate.delete(redisKey);if(Boolean.FALSE.equals(deleted)){thrownewSecurityException(State already consumed);}//
用 code 换取 access_tokenTaobaoTokenResponsetokenResptaobaoClient.getAccessToken(code);//
绑定推广关系openId 淘宝 sessionpromotionService.bindUser(authState.getOpenId(),tokenResp.getAccessToken(),authState.getItemId());//
跳回微信成功页StringredirectUrlhttps://wx.juwatech.cn/coupon/success?item_idauthState.getItemId();response.sendRedirect(redirectUrl);}catch(Exceptione){log.warn(OAuth callback failed,e);response.sendRedirect(https://wx.juwatech.cn/error?msgauth_failed);}}
AES 工具类实现CBC PKCS5Paddingpackagejuwatech.cn.common.crypto;publicclassAesUtils{publicstaticStringencrypt(StringplainText,Stringkey){try{byte[]keyBytesArrays.copyOf(key.getBytes(StandardCharsets.UTF_
,
;SecretKeySpeckeySpecnewSecretKeySpec(keyBytes,AES);CiphercipherCipher.getInstance(AES/CBC/PKCS5Padding);IvParameterSpecivnewIvParameterSpec(keyBytes);// 使用 key 前16字节作 IVcipher.init(Cipher.ENCRYPT_MODE,keySpec,iv);byte[]encryptedcipher.doFinal(plainText.getBytes(StandardCharsets.UTF_
);returnBase
getEncoder().encodeToString(encrypted);}catch(Exceptione){thrownewRuntimeException(AES encrypt failed,e);}}publicstaticStringdecrypt(StringencryptedBase64,Stringkey){try{byte[]keyBytesArrays.copyOf(key.getBytes(StandardCharsets.UTF_
,
;SecretKeySpeckeySpecnewSecretKeySpec(keyBytes,AES);CiphercipherCipher.getInstance(AES/CBC/PKCS5Padding);IvParameterSpecivnewIvParameterSpec(keyBytes);cipher.init(Cipher.DECRYPT_MODE,keySpec,iv);byte[]decryptedcipher.doFinal(Base
getDecoder().decode(encryptedBase
);returnnewString(decrypted,StandardCharsets.UTF_
;}catch(Exceptione){thrownewRuntimeException(AES decrypt failed,e);}}}
安全增强措施密钥轮换每季度更新JUWATECH_OAUTH_KEY_2026旧密钥保留 7 天兼容IP 绑定可选在AuthState中加入客户端 IP回调时校验速率限制对/oauth/callback接口按 IP 限流如 5 次/分钟。
上线后系统拦截了 1200 次重放攻击尝试授权成功率稳定在
9
7%。
本文著作权归 微赚淘客系统