紧接着上一篇的文章继续续写:
当第三次错误后等待30秒,则我们需要将登录次数userLoginAttempts重置为0才能继续登录。最初代码是在等待完30秒后,进行一次判断,当超过30秒,我们将userLoginAttempts重置为0,如之前代码所示:
//如果登录次数超过3次时就会报出还需要等待多少秒
if(res.getUserLoginAttempts()>2){
LocalDateTime lastLoginTime = res.getUserLoginLastTime();
LocalDateTime currentTime = LocalDateTime.now();
//判断
Duration duration = Duration.between(lastLoginTime,currentTime);
if (duration.getSeconds()<30){
long i = 30;
return Result.error("-1", "You still need to wait "+(i-duration.getSeconds())+" seconds");
}else {
userService.updateLoginAttemptsZero(res.getUserId());
return Result.error("-1","Login restrictions reset");
}
}
这样写的话就会在等待30秒重试后再次点击重置userLoginAttempts,这样就非常的不合理,就达不到所谓的登录错误3次等待30秒后再次登录这个需求。于是就思考了一个问题,如果在等待30秒错误的时候直接将userLoginAttempts重置为0,这样在提示框就不会出现Login restrictions reset。
一、使用定时器来解决这个问题,在修改第三次错误登录时间这个接口的时候,通过异步@Async的方式使userLoginAttempts重置为0。
二、使用的数据库为mysql,则使用mysql里的触发器来解决这个问题。
三、使用redis的自动过期机制解决这个问题,当用户登录次数2的时候,将用户的邮箱号userEmail作为键,用户的userIds作为值存储至redis中,再通过onMessage判断userEmail是否存在,如果不存在直接将userLoginAttempts重置为0,代码如下:
if(res.getUserLoginAttempts() == 2){
res.setUserLoginLastTime(LocalDateTime.now());
// 2.将我们的token存放到rdis中
redisUtils.setString(res.getUserEmail(), res.getUserIds() + "", 30L);
userService.updateuserLoginLastTime(res);
return Result.error("-1","You will need to wait 30 seconds before logging in");
}
redis监听器代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
重置userLoginAttempts代码如下:
import lombok.extern.slf4j.Slf4j;
import org.cqipc.edu.bean.User;
import org.cqipc.edu.mapper.UserMapper;
import org.cqipc.edu.service.UserService;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer){
super(listenerContainer);
}
@Resource
private UserMapper userMapper;
@Resource
private UserService userService;
/*
使用该方法监听,当我们的key失效的时候执行该方法
*/
@Override
public void onMessage(Message message, byte[] pattern){
String expirakey = message.toString();
// log.info("该key :expiraKey:" + expirakey + "失效啦");
log.error("该key :expiraKey:" + expirakey + "失效啦");
User userEmail = userMapper.getUserEmail(expirakey);
if (userEmail == null){
return;
}
userService.updateLoginAttemptsZero(userEmail.getUserId());
}
}
这样就能实现这个功能,完整代码地址:student-erp: student-erp后端基于Spring Boot框架,前端基于vue3框架,使用Sping Security作为安全框架,目前专注于对各个院校的管理,针对到学生/教师/管理员、班级、专业、学院、大学之间的权限管理问题。学生/教师/管理员直接通过用户表,角色表来区分三者之间的区别。