做視頻網(wǎng)站新手教學(xué)近期國際新聞
Redis+lua腳本限制ip多次輸入錯誤密碼
不能鎖username,因?yàn)槿绻腥藧阂獗A羝平饷艽a的話。會導(dǎo)致用戶本人無法登錄。
這里我采用 以ip的方式進(jìn)行鎖定。利用redis
設(shè)置key:ip。value:當(dāng)前ip嘗試登錄的次數(shù)
實(shí)現(xiàn)邏輯
邏輯簡單,假設(shè)限制錯誤次數(shù)為5
- 用戶登錄時。判斷key是否存在。
- 如果key不存在,說明第一次登錄。判斷密碼是否正確,如果正確什么都不干直接返回。如果不正確,設(shè)置key為ip,value為1,并設(shè)置過期時間,返回錯誤信息。
- 如果存在key,說明之前嘗試登錄過。判斷value是否大于閾值,如果大于閾值刷新過期 并返回錯誤。如果小于閾值,還要判斷密碼是否正確。如果密碼正確,刪除key并返回正確信息。如果密碼不正確,將key的value值+1并重置過期時間,返回密碼錯誤的信息。
以上主要注意兩點(diǎn)
一點(diǎn)是有key的情況下密碼正確需要刪除key。
二是有key的情況下,密碼錯誤或者嘗試次數(shù)大于閾值再次嘗試登錄,需要刷新過期時間。
使用lua腳本主要保證了對上述邏輯的原子性,因?yàn)樯婕矮@取key的值并判斷,然后將key的值+1 或 刪除key。
實(shí)現(xiàn)代碼
Service類加載時,加載lua腳本
private static final DefaultRedisScript<Long> CHECK_LOGIN_SCRIPT;// 類加載時 加載lua腳本static {CHECK_LOGIN_SCRIPT = new DefaultRedisScript<>();CHECK_LOGIN_SCRIPT.setLocation(new ClassPathResource("checkLogin.lua"));CHECK_LOGIN_SCRIPT.setResultType(Long.class);}
具體校驗(yàn)方法,主要邏輯調(diào)用了lua腳本
private void checkLoginInfo(UserLoginContext userLoginContext) {String username = userLoginContext.getUsername();String password = userLoginContext.getPassword();//根據(jù)用戶名查實(shí)體 從數(shù)據(jù)庫RPanUser entity = getRPanUserByUsername(username);if (Objects.isNull(entity)){throw new RPanBusinessException("用戶名不存在");}String salt = entity.getSalt(); //獲取鹽值String encPassword = PasswordUtil.encryptPassword(salt, password) ; //將前端傳 的密碼加密String dbPassword = entity.getPassword(); //獲取數(shù)據(jù)庫的密碼// encPassword 表示前端傳過來的 加密后的密碼// dbPassword 表示數(shù)據(jù)庫中的 加密后的密碼// TODO 調(diào)用lua腳本判斷 登錄失敗 登錄成功 及鎖定ipList<String> keys = new ArrayList<>();String ipAddress = HttpLogEntityBuilder.getIpAddress(userLoginContext.getRequest()); //獲取請求ip// key設(shè)置為 ip+username,進(jìn)行鎖定keys.add(UserConstants.CHECK_LOGIN_PREFIX + ipAddress + "_" + username);// 執(zhí)行l(wèi)ua腳本Long result = (Long)redisTemplate.execute(CHECK_LOGIN_SCRIPT, keys, encPassword, dbPassword, UserConstants.CHECK_LOGIN_THRESHOLD, UserConstants.CHECK_LOGIN_EXPIRE);if (result == 0){throw new RPanBusinessException("用戶名或密碼不正確");}if (result == -1){throw new RPanBusinessException("輸入密碼錯誤達(dá)到"+UserConstants.CHECK_LOGIN_THRESHOLD+"次,請1分鐘后嘗試");}
// if (!Objects.equals(encPassword, dbPassword)) {
// throw new RPanBusinessException("密碼信息不正確");
// }//填入實(shí)體信息userLoginContext.setEntity(entity);}
lua腳本,放在resources目錄下
-- 判斷用戶登錄的lua腳本local key1 = KEYS[1]
local password1 = ARGV[1]
local password2 = ARGV[2]
-- 閾值
local threshold = ARGV[3]
-- 過期時間
local expiretime = ARGV[4]-- 判斷key是否存在
local value = redis.call('get', key1)-- 有key
if value thenif(value >= threshold) thenredis.call('expire', key1, expiretime) -- 刷新過期時間return -1 -- 輸入密碼錯誤達(dá)到threshold次,請1分鐘后嘗試endif(password1 == password2) then -- 校驗(yàn)正確,刪除key并返回1表示通過redis.call('del', key1)return 1elseredis.call('INCR', key1) -- key的值+1redis.call('expire', key1, expiretime) -- 刷新過期時間return 0 -- 用戶名或密碼不正確end
end-- 沒有key
if(password1 == password2) thenreturn 1
elseredis.call('setex', key1, expiretime, 1) -- setex key [過期時間] 1return 0 -- 用戶名或密碼不正確
end