SpringBoot - 优雅的实现【账号登录错误次数的限制和锁定】
创作时间:
作者:
@小白创作中心
SpringBoot - 优雅的实现【账号登录错误次数的限制和锁定】
引用
CSDN
1.
https://blog.csdn.net/yangshangwei/article/details/144059370
Pre
SpringBoot - 优雅的实现【流控】
需求
需求描述:
- 登录错误次数限制:在用户登录时,记录每个账号的登录错误次数,并限制连续错误的次数。
- 账号锁定机制:当一个账号连续输入错误密码超过5次时,该账号将被锁定15分钟。在15分钟后,账号会自动解锁。
- 自动解锁功能:账号在连续错误输入超过5次后,将触发锁定机制,并且5分钟后自动解锁,利用Redis的键值存储来管理错误次数和锁定时间。
- 配置文件:登录错误次数的限制(如5次错误)和账号锁定时间(如15分钟)应该能通过配置文件进行设置,以便灵活配置。
- 自定义注解实现:使用自定义注解来实现登录错误次数限制与账号锁定功能的逻辑。
技术细节:
- 使用Redis的Key来存储和管理每个用户的错误登录次数和锁定状态。
- 自定义注解实现错误次数和锁定时长的判断与控制。
- 错误次数和锁定时长通过配置文件(如
application.yml或application.properties)进行配置,支持灵活调整。
实现步骤
简易实现
1. 添加依赖
首先,在pom.xml中添加必要的依赖:
<dependencies>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
2. 配置文件
在application.yml中配置相关参数:
3. 自定义注解
创建一个自定义注解@LoginAttemptLimit:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginAttemptLimit {
// 默认值依赖配置文件,可在此处设定默认值
int maxAttempts() default 5;
int lockTime() default 15;
}
4. AOP切面
创建一个AOP切面来处理登录错误次数的限制和锁定逻辑:
@Aspect
@Slf4j
@Component
public class LoginAttemptLimitAspect {
@Resource
private LoginAttemptValidator loginAttemptValidator;
@Resource
private AdminAuthService authService;
@Value("${supervision.max-attempts:2}")
private int maxAttempts;
@Value("${supervision.lock-time:5}")
private long lockTime;
@Around("@annotation(loginAttemptLimit)")
public Object limitLoginAttempts(ProceedingJoinPoint joinPoint, LoginAttemptLimit loginAttemptLimit) throws Throwable {
String attemptKey = "";
String lockKey = "";
// 根据登录类型获取对应的键
// # 0 账号密码模式 1 key模式(默认)
if (authService.getLoginType() == 1) {
AuthKeyLoginReqVO authKeyLoginReqVO = (AuthKeyLoginReqVO) joinPoint.getArgs()[0];
attemptKey = RedisKeyConstants.ATTEMP_KEY_PREFIX + authKeyLoginReqVO.getUsername();
lockKey = RedisKeyConstants.LOCK_KEY_PREFIX + authKeyLoginReqVO.getUsername();
} else {
AuthLoginReqVO authLoginReqVO = (AuthLoginReqVO) joinPoint.getArgs()[0];
attemptKey = RedisKeyConstants.ATTEMP_KEY_PREFIX + authLoginReqVO.getUsername();
lockKey = RedisKeyConstants.LOCK_KEY_PREFIX + authLoginReqVO.getUsername();
}
// 检查账号是否已被锁定
if (loginAttemptValidator.isLocked(lockKey)) {
throw new ServiceException(TOO_MANY_REQUESTS.getCode(), "账号被锁定,请稍后重试");
}
// 获取登录次数
int attempts = loginAttemptValidator.getAttempt(attemptKey);
// 检查登录尝试次数是否超过最大限制
if (attempts >= maxAttempts) {
loginAttemptValidator.setLock(lockKey, lockTime);
loginAttemptValidator.resetAttempt(attemptKey);
throw new ServiceException(TOO_MANY_REQUESTS.getCode(), "账号被锁定,请稍后重试");
}
try {
// 执行登录操作
Object result = joinPoint.proceed();
// 登录成功,重置登录尝试计数
loginAttemptValidator.resetAttempt(attemptKey);
return result;
} catch (Exception e) {
// 登录失败,增加登录尝试计数
loginAttemptValidator.incrementAttempt(attemptKey);
throw e;
}
}
}
5. 使用自定义注解:
在服务的方法上添加自定义注解
6. 测试
创建一个控制器来处理登录请求
连续错误5次后,
Redis中Key的TTL
附
import cn.hutool.core.util.ObjectUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class LoginAttemptValidator {
@Resource
private RedisTemplate<String,Integer> redisTemplate;
/**
* 尝试次数自增
*
* @param key Redis中的键,用于标识特定的尝试计数
*/
public void incrementAttempt(String key) {
redisTemplate.opsForValue().increment(key);
}
/**
* 获取尝试次数
* 如果给定键的尝试次数在缓存中不存在,则初始化尝试次数为0
* 此方法主要用于跟踪某些操作的尝试次数,例如登录尝试次数,以防止暴力破解
*
* @param key 缓存中的键,通常与特定用户或操作相关联
* @return 尝试次数如果缓存中没有对应的尝试记录,则返回0
*/
public int getAttempt(String key) {
// 从Redis中获取尝试次数
Integer attempts = redisTemplate.opsForValue().get(key);
// 如果尝试次数为空,则初始化尝试次数
if (attempts == null ) initAttempt(key);
// 返回尝试次数如果为null,则返回0
return attempts == null ? 0 : attempts;
}
/**
* 初始化尝试次数
* 该方法用于在Redis中初始化一个键的尝试次数为0
* 主要用于记录和管理操作的尝试次数,以便进行后续的限制或监控
*
* @param key Redis中的键,用于唯一标识一个操作或请求
*/
public void initAttempt(String key) {
redisTemplate.opsForValue().set(key, 0);
}
/**
* 重置尝试次数
* 通过删除Redis中的键来重置特定尝试的计数
*
* @param key Redis中用于标识尝试计数的键
*/
public void resetAttempt(String key) {
redisTemplate.delete(key);
}
/**
* 设置缓存锁
*
* @param key 锁的唯一标识,通常使用业务键作为锁的key
* @param duration 锁的持有时间,单位为分钟
*
* 此方法旨在通过Redis实现分布式锁的功能,通过设置一个具有过期时间的键值对来实现
* 键值对的key为业务键,值为锁定标志,过期时间由参数duration指定
*/
public void setLock(String key, long duration) {
redisTemplate.opsForValue().set(key, RedisKeyConstants.LOCK_FLAG, duration, TimeUnit.MINUTES);
}
/**
* 检查给定键是否处于锁定状态
*
* @param key 要检查的键
* @return 如果键未锁定,则返回false;如果键已锁定,则返回true
*/
public boolean isLocked(String key) {
// 从Redis中获取键对应的值
Integer value = redisTemplate.opsForValue().get(key);
// 如果值为空,则表明键未锁定,返回false
if (ObjectUtil.isEmpty(value)) return false ;
// 比较键的值是否与锁定标志相等,如果相等则表明键已锁定,返回true
return RedisKeyConstants.LOCK_FLAG == redisTemplate.opsForValue().get(key);
}
}
总结
基于Spring Boot的账号登录错误次数限制和锁定功能,使用了Redis来存储登录失败次数和锁定状态,并通过自定义注解和AOP来实现切面逻辑。配置文件中可以灵活配置最大尝试次数和锁定时长。
热门推荐
英国肺结核治疗中的副作用及管理指南
2025年迁坟最佳吉日全新一览表(1月-12月)
被DeepSeek对《哪吒2》无量仙翁的解读惊到,文科博士:我白读了?
健康饮食不仅护心还能健脑!
购买二手理想ONE必读指南:细节验车、合同签订及售后服务全面解析
甲醛检测仪原理是什么?如何确保检测结果更准确?
古村落的文化之旅:探访安徽宏村与云南沙溪
十二星座英文名称及发音
冰汽时代2怎么规划布局?《冰汽时代2》规划布局思路分享
碘伏可以擦脸吗
碘伏消毒的正确使用方法是什么
磁力泵隔离套作用
《原神》官方与同人绘画精选集:探索游戏角色与场景的艺术创作全览
互联网电商,自营与平台模式的异同理解
一根头发半截黑半截白,是咋回事?
不废江河万古流——书写文化传承发展的当涂新画卷
天麻好搭档与避讳食物,健康食用小贴士
IT技术支持工程师的面试技巧
固执不只是性格?神经科学揭示大脑的固执密码
八部顶级搞笑动漫大盘点,笑到肚子疼的夏日消暑良品
固态电池时代来临,新能源车会淘汰吗?燃油车命运又将如何?
缓解烦躁焦虑不安的小妙招
到明年年底,南海至少三成社区卫生服务中心将转型为热敏灸特色站
如何接触投资团队
用八字选姓名,根据八字取名字有没有科学依据
二月初十“长寿日”,“1事不做,2样要吃,忌1事”,福寿安康
我的世界埋葬的宝藏怎么找:坐标指令代码详解
上海九院突破血管瘤诊疗难题,建立国际领先诊疗体系
治疗三叉神经痛的首选药物及注意事项
女生学习八字命理学,哪种流派或方法最适合呢