如何解決重復下單的問題?(下單防重,重放攻擊)
問題來源:
近日發現某些用戶對一些商品進行多次下單,而且比較頻繁,而且下單時間都在同一秒內,懷疑產生重復請求
問題描述:
用戶在商品頁面,多次點擊下單按鈕,后台怎么知道是用戶對一個商品進行多次下單,還是人工誤操作或者客戶端異常進行重復請求下單了呢?
解決方案:
- 進入商品頁面,就是下單頁面的時候,產生一個不重復的隨機數。
- 下單的時候把這個隨機數帶上
- 下單校驗的時候,利用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
