如何解決重復下單的問題?(下單防重,重放攻擊)
問題來源:
近日發現某些用戶對一些商品進行多次下單,而且比較頻繁,而且下單時間都在同一秒內,懷疑產生重復請求
問題描述:
用戶在商品頁面,多次點擊下單按鈕,后台怎么知道是用戶對一個商品進行多次下單,還是人工誤操作或者客戶端異常進行重復請求下單了呢?
解決方案:
- 進入商品頁面,就是下單頁面的時候,產生一個不重復的隨機數。
- 下單的時候把這個隨機數帶上
- 下單校驗的時候,利用Redis緩存鎖,先鎖這個隨機數,再做業務處理,做完再釋放。
問題升級:
如果客戶端重復的下單請求過多,或者后台處理過快,就會產生所謂的重放攻擊。
如,客戶端請求模擬
請求A 20:18:01 xxx/order?storeid=1&randomNum=abcd123 用戶下單
請求A 獲得鎖成功,開始處理
請求B 20:18:01 xxx/order?storeid=1&randomNum=abcd123 用戶下單
請求B 獲得鎖失敗
請求C 20:18:01 xxx/order?storeid=1&randomNum=abcd123 用戶下單
請求C 獲得鎖失敗
請求A 下單成功,釋放鎖
請求D 20:18:01 xxx/order?storeid=1&randomNum=abcd123 用戶下單
請求D 獲得鎖成功,開始處理
請求D 下單成功,釋放鎖
針對同一個隨機數randomNum=abcd123居然下單成功2次,這就是重復攻擊帶來的危害
問題升級,解決方案:
- 下單校驗的時候,利用Redis緩存鎖,先鎖這個隨機數,鎖成功之后,判斷隨機數是否曾經處理過,如果沒有就把隨機數加入緩存設置時長為X,再做業務處理,做完再釋放
/** * 獲得鎖 * @param lockId * @return */ public boolean getLock(String lockId) { try { String KEY_LOCK_ID_="LOCK_ID_"; String KEY_LOCK_HIS_ID_="LOCK_HIS_ID_"; Boolean success = redisTemplate.opsForValue().setIfAbsent(KEY_LOCK_ID_+lockId, "lock"); //解決重放攻擊 if(success != null && success){ if(hasKey(KEY_LOCK_HIS_ID_+lockId)){ success = false; logger.error("【REDIS操作】【獲得鎖失敗】【已存在歷史鎖碼】【懷疑重放攻擊:"+lockId+"】"); }else{ success = true; set(KEY_LOCK_HIS_ID_+lockId,lockId,60*60*24); } } return success; } catch (Exception e) { logger.error("【REDIS操作】【獲得鎖錯誤】",e); return false; } } /** * 釋放鎖 * @param lockId */ public void releaseLock(String lockId) { try { String KEY_LOCK_ID_="LOCK_ID_"; redisTemplate.delete(KEY_LOCK_ID_ + lockId); } catch (Exception e) { logger.error("【REDIS操作】【釋放鎖錯誤】",e); } }
問題描述:用戶下訂單購買,因為各種原因(網絡卡,快遞點擊等)重復提交2個或者以上一模一樣的訂單,由於是同時提交的,第一個訂單執行扣款生成訂單未完成時候,第二個已經進來了,導致付一筆錢購買了2次或多次商品 解決方案:
1、緩存lock,緩存此用戶的操作行為,注意緊緊緩存操作的標志,下次進入判斷此標志是否存在,存在即不進入數據庫事務
2、應用程序application lock,和1相比,會阻塞其他用戶的正常行為
3、模仿銀行扣款機制,數據表建一個隨機唯一標志,每次請求帶上這個標志,操作的同時進行修改這個標志
4、應用程序生成唯一標志,數據庫做字段的唯一索引
5、扣款為負數的事務進行回滾
6、使用事務的隔離級別
7、使用redis的incr控制用戶的並發數,memcache的add也可以實現這種效果,memcached借助cas