很多地方都要用到重試次數限制,不然就會被暴力破解。比如登錄密碼。
下面不是完整代碼,只是偽代碼,提供一個思路。
第一種(先聲明,這樣寫有個bug)
import java.text.MessageFormat; public class Demo { /** * 限制次數 */ private static final Integer MAX_TIMES = 5; /** * 鎖定時間(也是key的失效時間) */ private static final Integer LIMIT_TIME = 3; /** * key */ private static final String LIMIT_TIMES_KEY = "LimitTimesKey:%s"; public void deal(String phone, String password){ // 用戶id Long userId = 6815356L; // 組裝key String key = MessageFormat.format(LIMIT_TIMES_KEY, userId); // 先獲取key對應的value String s = redisService.get(key); int currentTimes = s != null ? Integer.parseInt(s) : 0; // 如果當前次數為[LIMIT_TIMES]次或以上,則拋異常 if(currentTimes >= MAX_TIMES){ throw new RuntimeException("請在"+ LIMIT_TIME +"分鍾后繼續嘗試"); } // TODO 做其它邏輯,比如登錄操作 Integer code = userService.login(phone, password); // 比如密碼不正確的狀態碼是10086 if(code == 10086){ // 失敗次數+1 int thisTimes = currentTimes + 1; String value = String.valueOf(thisTimes); // 如果小於最大限制 if(thisTimes < MAX_TIMES){ redisService.set(key, value); throw new RuntimeException("原密碼錯誤,還可以重試"+ (MAX_TIMES - thisTimes) +"次"); }else{ redisService.setex(key, LIMIT_TIME*60, value); throw new RuntimeException("原密碼錯誤,請在"+ LIMIT_TIME +"分鍾后繼續嘗試"); } } // 登陸成功,清理redis redisService.del(key); } }
以上代碼思路:
以上代碼有什么問題呢:當失敗次數小於最大限制,那里直接set了一個值,沒有設置失效時間。如果用戶失敗了一次就不再嘗試了,那么我們設置的key就會永遠存在;同時用戶在n年后再去登陸,他擁有的重試次數是凌駕於n年前的重試次數之上的,也就是說我今年浪費了1次重試次數,還剩下4次,我明年再重試,我能夠重試的次數就不是5而是4了,因為我的重試次數記錄一直存在。
import java.text.MessageFormat; public class Demo { /** * 限制次數 */ private static final Integer MAX_TIMES = 5; /** * 鎖定時間(也是key的失效時間) */ private static final Integer LIMIT_TIME = 3; /** * key */ private static final String LIMIT_TIMES_KEY = "LimitTimesKey:%s"; public void deal(String phone, String password){ // 用戶id Long userId = 6815356L; // 組裝key String key = MessageFormat.format(LIMIT_TIMES_KEY, userId); // 先獲取key對應的value String s = redisService.get(key); int currentTimes = s != null ? Integer.parseInt(s) : 0; // 如果當前次數為[LIMIT_TIMES]次或以上,則拋異常 if(currentTimes >= MAX_TIMES){ throw new RuntimeException("請在"+ LIMIT_TIME +"分鍾后繼續嘗試"); } // TODO 做其它邏輯,比如登錄操作 Integer code = userService.login(phone, password); // 比如密碼不正確的狀態碼是10086 if(code == 10086){ // 失敗次數+1 int thisTimes = currentTimes + 1; String value = String.valueOf(thisTimes); // 設置值,重點是失效時間 redisService.setex(key, LIMIT_TIME*60, value); // 如果小於最大限制 if(thisTimes < MAX_TIMES){ throw new RuntimeException("原密碼錯誤,還可以重試"+ (MAX_TIMES - thisTimes) +"次"); }else{ throw new RuntimeException("原密碼錯誤,請在"+ LIMIT_TIME +"分鍾后繼續嘗試"); } } // 登陸成功,清理redis redisService.del(key); } }
改進之后的思路如下: