很多地方都要用到重試次數限制,不然就會被暴力破解。比如登錄密碼。
下面不是完整代碼,只是偽代碼,提供一個思路。
第一種(先聲明,這樣寫有個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);
}
}
改進之后的思路如下:

