SpringBoot - 优雅的实现【账号登录错误次数的限制和锁定】

embedded/2024/11/27 15:23:22/

文章目录

  • Pre
  • 需求
  • 实现步骤
  • 简易实现
    • 1. 添加依赖
    • 2. 配置文件
    • 3. 自定义注解
    • 4. AOP切面
    • 5. 使用自定义注解:
    • 6. 测试
  • 总结

在这里插入图片描述


Pre

SpringBoot - 优雅的实现【流控】


需求

需求描述:

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

技术细节:

  • 使用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 {@Resourceprivate LoginAttemptValidator loginAttemptValidator;@Resourceprivate 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 {@Resourceprivate 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,则返回0return 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);// 如果值为空,则表明键未锁定,返回falseif (ObjectUtil.isEmpty(value)) return  false ;// 比较键的值是否与锁定标志相等,如果相等则表明键已锁定,返回truereturn RedisKeyConstants.LOCK_FLAG == redisTemplate.opsForValue().get(key);}
}

总结

在这里插入图片描述

基于Spring Boot的账号登录错误次数限制和锁定功能,使用了Redis来存储登录失败次数和锁定状态,并通过自定义注解和AOP来实现切面逻辑。配置文件中可以灵活配置最大尝试次数和锁定时长。


在这里插入图片描述


http://www.ppmy.cn/embedded/140931.html

相关文章

Conda命令速查

命令速查 命令Command新建Python环境conda create --name hello python3.10.14激活环境conda activate hello退出当前环境conda deactivate安装python包conda install < package name>安装python包pip install < package name>查看所有python环境conda env list

嵌入式系统与OpenCV

目录 一、OpenCV 简介 二、嵌入式 OpenCV 的安装方法 1. Ubuntu 系统下的安装 2. 嵌入式 ARM 系统中的安装 3. Windows10 和树莓派系统下的安装 三、嵌入式 OpenCV 的性能优化 1. 介绍嵌入式平台上对 OpenCV 进行优化的必要性。 2. 利用嵌入式开发工具&#xff0c;如优…

分布式查询处理优化之数据分片

基本的数据分布策略 数据分片 分片是将分布式数据库的全局数据逻辑划分为关系片段并且进行实际的物理分配的过程。不同的分布式系统有着不同的分片策略。 关系数据库主要通过数据分片技术对全局数据进行逻辑划分和实际的物理分配。考虑的主要因素&#xff1a;数据的模式特征…

Rust赋能前端: 纯血前端将 Table 导出 Excel

❝ 人的本事靠自己&#xff0c;人的成长靠网络 大家好&#xff0c;我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder ❝ 此篇文章所涉及到的技术有 Rust( Rust接收json对象并解析/Rust生成xml) WebAssembly 表格合并(静态/动态) React/Vue表格导出 excel Rspac…

一篇文章读懂 Prettier CLI 命令:从基础到进阶 (3)

Prettier 命令行工具 Prettier 提供了一个强大的命令行界面 (CLI)&#xff0c;允许用户通过命令行来格式化代码。在 package.json 中&#xff0c;你可以配置一个脚本来运行 Prettier&#xff0c;例如&#xff1a; "scripts": {"format": "prettier …

vscode查找函数调用

在 VS Code 中&#xff0c;要查找 C 语言函数的调用列表&#xff0c;有以下几种方法可以使用&#xff0c;具体取决于项目的规模和你的需求&#xff1a; 方法 1: 使用全局查找功能 步骤&#xff1a; 打开全局查找&#xff1a; 按 CtrlShiftF (Windows/Linux) 或 CmdShiftF (Ma…

AQS底层原理

AQS底层原理 详细版本&#xff1a;https://blog.csdn.net/m0_73866527/article/details/142518162?spm1001.2014.3001.5501 AQS架构 AQS核心思想 AQS使用一个Volatile的int类型的成员变量State来表示同步状态。通过CAS完成对State值的修改来获得锁。未获得锁的线程放在内置…

Prometheus告警带图完美解决方案

需求背景 告警分析处理流程 通常我们收到 Prometheus 告警事件通知后&#xff0c;往往都需要登录 Alertmanager 页面查看当前激活的告警&#xff0c;如果需要分析告警历史数据信息&#xff0c;还需要登录 Prometheus 页面的在 Alerts 中查询告警 promQL 表达式&#xff0c;然…