Redis+lua脚本限制ip多次输入错误密码

Redis+lua脚本限制ip多次输入错误密码

不能锁username,因为如果有人恶意保留破解密码的话。会导致用户本人无法登录。

这里我采用 以ip的方式进行锁定。利用redis

设置key:ip。value:当前ip尝试登录的次数

实现逻辑

逻辑简单,假设限制错误次数为5

  1. 用户登录时。判断key是否存在。
  2. 如果key不存在,说明第一次登录。判断密码是否正确,如果正确什么都不干直接返回。如果不正确,设置key为ip,value为1,并设置过期时间,返回错误信息。
  3. 如果存在key,说明之前尝试登录过。判断value是否大于阈值,如果大于阈值刷新过期 并返回错误。如果小于阈值,还要判断密码是否正确。如果密码正确 ,删除key并返回正确信息。如果密码不正确,将key的value值+1并重置过期时间,返回密码错误的信息。

以上主要注意两点

一点是有key的情况下密码正确需要删除key

二是有key的情况下,密码错误或者尝试次数大于阈值再次尝试登录,需要刷新过期时间

使用lua脚本主要保证了对上述逻辑的原子性,因为涉及获取key的值并判断,然后将key的值+1 或 删除key。
实现代码
Service类加载时,加载lua脚本

java 复制代码
    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);
    }

具体校验方法,主要逻辑调用了lua脚本

java 复制代码
    private void checkLoginInfo(UserLoginContext userLoginContext) {
        String username = userLoginContext.getUsername();
        String password = userLoginContext.getPassword();

        //根据用户名查实体 从数据库
        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();   //获取数据库的密码
        // encPassword 表示前端传过来的 加密后的密码
        // dbPassword  表示数据库中的   加密后的密码
        // TODO 调用lua脚本判断 登录失败 登录成功 及锁定ip
        List<String> keys = new ArrayList<>();
        String ipAddress = HttpLogEntityBuilder.getIpAddress(userLoginContext.getRequest());    //获取请求ip
        // key设置为 ip+username,进行锁定
        keys.add(UserConstants.CHECK_LOGIN_PREFIX + ipAddress + "_" + username);
        // 执行lua脚本
        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("输入密码错误达到"+UserConstants.CHECK_LOGIN_THRESHOLD+"次,请1分钟后尝试");
        }
//        if (!Objects.equals(encPassword, dbPassword)) {
//            throw new RPanBusinessException("密码信息不正确");
//        }
        //填入实体信息
        userLoginContext.setEntity(entity);
    }

lua脚本,放在resources目录下

lua 复制代码
-- 判断用户登录的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 then
    if(value >= threshold) then
        redis.call('expire', key1, expiretime)  -- 刷新过期时间
        return -1                               -- 输入密码错误达到threshold次,请1分钟后尝试
    end

    if(password1 == password2) then -- 校验正确,删除key并返回1表示通过
        redis.call('del', key1)
        return 1
    else
        redis.call('INCR', key1)               -- key的值+1
        redis.call('expire', key1, expiretime) -- 刷新过期时间
        return 0                                -- 用户名或密码不正确
    end
end

-- 没有key
if(password1 == password2) then
    return 1
else
    redis.call('setex', key1, expiretime, 1) -- setex key [过期时间] 1
    return 0   -- 用户名或密码不正确
end
相关推荐
04Koi.7 小时前
Redis--常用数据结构和编码方式
数据库·redis·缓存
芷栀夏7 小时前
如何在任何地方随时使用本地Jupyter Notebook无需公网IP
服务器·ide·tcp/ip·jupyter·ip
火云洞红孩儿8 小时前
基于AI IDE 打造快速化的游戏LUA脚本的生成系统
c++·人工智能·inscode·游戏引擎·lua·游戏开发·脚本系统
xserver210 小时前
ensp 基于EASY IP的公司出口链路配置
网络·tcp/ip·智能路由器
weisian15110 小时前
Redis篇--常见问题篇8--缓存一致性3(注解式缓存Spring Cache)
redis·spring·缓存
HEU_firejef10 小时前
Redis——缓存预热+缓存雪崩+缓存击穿+缓存穿透
数据库·redis·缓存
weisian15111 小时前
Redis篇--常见问题篇7--缓存一致性2(分布式事务框架Seata)
redis·分布式·缓存
白云coy11 小时前
Redis 安装部署[主从、哨兵、集群](linux版)
linux·redis
Logintern0911 小时前
Linux如何设置redis可以外网访问—执行使用指定配置文件启动redis
linux·运维·redis
P.H. Infinity12 小时前
【Redis】配置序列化器
数据库·redis·缓存