SpringBoot实现账号登录错误次数的限制和锁定功能

 更新时间:2024年12月02日 11:11:40   作者:小小工匠  
本文介绍了如何使用SpringBoot和Redis实现账号登录错误次数限制和锁定功能,通过自定义注解和AOP切面,结合配置文件灵活设置最大尝试次数和锁定时长,感兴趣的朋友跟随小编一起看看吧

Pre

SpringBoot - 优雅的实现【流控】

需求

需求描述:

  • 登录错误次数限制:在用户登录时,记录每个账号的登录错误次数,并限制连续错误的次数。
  • 账号锁定机制:当一个账号连续输入错误密码超过5次时,该账号将被锁定15分钟。在15分钟后,账号会自动解锁。
  • 自动解锁功能:账号在连续错误输入超过5次后,将触发锁定机制,并且5分钟后自动解锁,利用Redis的键值存储来管理错误次数和锁定时间。
  • 配置文件:登录错误次数的限制(如5次错误)和账号锁定时间(如15分钟)应该能通过配置文件进行设置,以便灵活配置。
  • 自定义注解实现:使用自定义注解来实现登录错误次数限制与账号锁定功能的逻辑。

技术细节:

  • 使用Redis的Key来存储和管理每个用户的错误登录次数和锁定状态。
  • 自定义注解实现错误次数和锁定时长的判断与控制。
  • 错误次数和锁定时长通过配置文件(如application.ymlapplication.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来实现切面逻辑。配置文件中可以灵活配置最大尝试次数和锁定时长。

到此这篇关于SpringBoot实现账号登录错误次数的限制和锁定功能的文章就介绍到这了,更多相关SpringBoot账号登录错误锁定内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot中扩展XML请求与响应的支持详解

    Spring Boot中扩展XML请求与响应的支持详解

    这篇文章主要给大家介绍了关于Spring Boot中扩展XML请求与响应的支持的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-09-09
  • spring在IoC容器中装配Bean详解

    spring在IoC容器中装配Bean详解

    这篇文章主要介绍了spring在IoC容器中装配Bean详解,具有一定借鉴价值,需要的朋友可以参考下
    2017-12-12
  • Spring5+SpringMvc+Hibernate5整合的实现

    Spring5+SpringMvc+Hibernate5整合的实现

    这篇文章主要介绍了Spring5+SpringMvc+Hibernate5整合的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 详解Spring如何注入静态变量

    详解Spring如何注入静态变量

    这篇文章主要为大家详细介绍了Spring是如何注入静态变量的,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解一下
    2023-06-06
  • Java程序执行时间的2种简单方法

    Java程序执行时间的2种简单方法

    这篇文章介绍了Java程序执行时间的2种简单方法,有需要的朋友可以参考一下
    2013-09-09
  • Java静态static与实例instance方法示例

    Java静态static与实例instance方法示例

    这篇文章主要为大家介绍了Java静态static与实例instance方法示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • springsecurity基于token的认证方式

    springsecurity基于token的认证方式

    本文主要介绍了springsecurity基于token的认证方式,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • spring单元测试之@RunWith的使用详解

    spring单元测试之@RunWith的使用详解

    这篇文章主要介绍了spring单元测试之@RunWith的使用详解,@RunWith 就是一个运行器,@RunWith(JUnit4.class) 就是指用JUnit4来运行,
    @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,需要的朋友可以参考下
    2023-12-12
  • IDEA性能优化设置(解决卡顿问题)

    IDEA性能优化设置(解决卡顿问题)

    在我们日常使用IDEA进行开发时,可能会遇到许多卡顿的瞬间,本文主要介绍了IDEA性能优化设置,非常具有实用价值,需要的朋友可以参考下
    2023-05-05
  • Java多线程编程中的两种常用并发容器讲解

    Java多线程编程中的两种常用并发容器讲解

    这篇文章主要介绍了Java多线程编程中的两种常用并发容器讲解,分别是ConcurrentHashMap与ConcurrentHashMap,需要的朋友可以参考下
    2015-12-12

最新评论