OTP本意是一次性口令,比如郵箱驗證碼,短信驗證碼,或者根據totp或者hotp生成的默認30秒一變的6位數字。
不過開發者要注意,必須要在驗證成功后失效那個驗證碼,不然就會導致重放攻擊。
對于郵箱驗證碼,服務器可將郵箱和驗證碼存入json數組中或redis中,驗證時查找匹配,成功則刪除該項,一般還應配置失效時間。
對于手機驗證碼,服務器要將手機號和驗證碼存入json數組中或redis中,驗證時查找匹配,成功則刪除該項,一般還應配置失效時間。
對于動態口令,則要采用另外的方式:
先準備一個歷史成功json數組,每項是一條json,包含用戶id,token,驗證時刻+禁止重復的時長
每驗證成功一條用戶和動態口令,則查數據是否有相同id,相同token并且驗證時間還在禁止時間段內,如果查到,則認定為重放攻擊,記錄日志,驗證不通過
如果沒有查到,那么如果該用戶歷史列表為空,則添加json記錄,否則更新替換記錄的token和驗證時刻+禁止重復時長。
注意一般動態口令驗證會有一個允許的窗口數,以平衡用戶時間和服務器時間的差異,這些前后窗口內的token值在當前驗證仍然是可以通過的,所以禁止重復時長至少要覆蓋之后的窗口時長。如果token時長是30秒,窗口是2,那么禁止重復時長應該設置為90秒。
如果用戶數量大,嫌內存空間占用多,可以考慮每次計算時刪除已經過期的條目,或者使用redis,在添加記錄時定義失效時間。
...
var loginfaillist=[];
var userotpsucclist=[];
...if (tokenValidates) {let histmatch=userotpsucclist.find(item=>((item.userid==matchuser.uid.toLowerCase())&&(item.token==obj.token)&&(item.invalid>now)))if (histmatch!=undefined) {addfail(now,clientip);logger.error("涉嫌重放攻擊 "+obj.email); errlog("涉嫌重放攻擊 "+obj.email);return res.json({ "msg":"涉嫌重放攻擊"});}histmatch=userotpsucclist.find(item=>(item.userid==matchuser.uid.toLowerCase()))if (histmatch==undefined) userotpsucclist.push({"userid":matchuser.uid.toLowerCase(),"token":"obj.token","invalid":now+200000})else { histmatch.token=obj.token; histmatch.invalid=now+200000; }loginfaillist=loginfaillist.filter(itm=>(itm.ip!=req.ip));logger.info("認證通過 "+matchuser.username+" "+clientip);res.json({"msg":"succ","userid":matchuser.uid.toLowerCase(),"username":matchuser.username});}