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

文章目录

  • 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 {

    @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来实现切面逻辑。配置文件中可以灵活配置最大尝试次数和锁定时长。


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/923639.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SRIO DRP动态速率配置说明(详细讲解)

目录 一、SRIO IP时钟结构 1、时钟内部结构 2、时钟直接的关系 3、时钟计算原理 ​二、SRIO DRP介绍 ​1、MMCM DRP配置(xapp888) 2、CPLL DRP配置(ug476) 关于CPLL DRP配置详细介绍&#xff1a; GTX中CPLL、QPLL DRP动态配置方法&#xff08;详解&#xff09;-CSDN博客…

动态规划之背包问题

0/1背包问题 1.二维数组解法 题目描述&#xff1a;有一个容量为m的背包&#xff0c;还有n个物品&#xff0c;他们的重量分别为w1、w2、w3.....wn&#xff0c;他们的价值分别为v1、v2、v3......vn。每个物品只能使用一次&#xff0c;求可以放进背包物品的最大价值。 输入样例…

推荐一款龙迅HDMI2.0转LVDS芯片 LT6211UX LT6211UXC

龙迅的HDMI2.0转LVDS芯片LT6211UX和LT6211UXC是两款高性能的转换器芯片&#xff0c;它们在功能和应用上有所差异&#xff0c;同时也存在一些共同点。以下是对这两款芯片的详细比较和分析&#xff1a; 一、LT6211UX 主要特性&#xff1a; HDMI2.0至LVDS和MIPI转换器。HDMI2.0输…

深度学习模型:循环神经网络(RNN)

一、引言 在深度学习的浩瀚海洋里&#xff0c;循环神经网络&#xff08;RNN&#xff09;宛如一颗独特的明珠&#xff0c;专门用于剖析序列数据&#xff0c;如文本、语音、时间序列等。无论是预测股票走势&#xff0c;还是理解自然语言&#xff0c;RNN 都发挥着举足轻重的作用。…

[STM32]从零开始的STM32 FreeRTOS移植教程

一、前言 如果能看到这个教程的话&#xff0c;说明大家已经学习嵌入式有一段时间了。还记得嵌入式在大多数时候指的是什么吗&#xff1f;是的&#xff0c;我们所说的学习嵌入式大部分时候都是在学习嵌入式操作系统。从简单的一些任务状态机再到复杂一些的RTOS&#xff0c;再到最…

《操作系统 - 清华大学》5 -4:虚拟技术

文章目录 0. 虚拟存储的定义1. 目标2.局部性原理3. 虚拟存储的思路与规则4. 虚拟存储的基本特征5. 虚拟页式存储管理5.1 页表表项5.2 示例 0. 虚拟存储的定义 1. 目标 虚拟内存管理技术&#xff0c;简称虚存技术。那为什么要虚存技术&#xff1f;在于前面覆盖和交换技术&#…

2024APMCM亚太杯数学建模C题【宠物行业】原创论文分享

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年APMCM亚太地区大学生数学建模竞赛C题的成品论文。 给大家看一下目录吧&#xff1a; 目录 摘 要&#xff1a; 10 一、问题重述 14 二&#xff0e;问题分析 15 2.1问题一 15 2.2问题二 15 2.3问题三…

YOLOv8模型pytorch格式转为onnx格式

一、YOLOv8的Pytorch网络结构 model DetectionModel((model): Sequential((0): Conv((conv): Conv2d(3, 64, kernel_size(3, 3), stride(2, 2), padding(1, 1))(act): SiLU(inplaceTrue))(1): Conv((conv): Conv2d(64, 128, kernel_size(3, 3), stride(2, 2), padding(1, 1))(a…

零基础3分钟快速掌握 ——Linux【终端操作】及【常用指令】Ubuntu

1.为啥使用Linux做嵌入式开发 能广泛支持硬件 内核比较高效稳定 原码开放、软件丰富 能够完善网络通信与文件管理机制 优秀的开发工具 2.什么是Ubuntu 是一个以桌面应用为主的Linux的操作系统&#xff0c; 内核是Linux操作系统&#xff0c; 具有Ubuntu特色的可视…

VScode 连不上远程云服务器

今天下午写代码&#xff0c;打开 VScode 突然发现连不上云服务器了&#xff0c;一开始以为自己密码输错了&#xff0c;试了好多次&#xff0c;依然是这样的 经过查资料发现&#xff0c;应该是版本的自动升级导致的&#xff01;解决方案如下&#xff1a; 1、删除 windows 端的 …

图像分割——区域增长

一 区域增长 图像灰度阈值分割技术都没有考虑到图像像素空间的连通性。区域增长法则正好相反,顾及像素的连接性. 方法&#xff1a;1&#xff09;选择一个或一组种子&#xff1b; 2&#xff09;选择特征及相似性判决准则&#xff1b; 3&#xff09;从该种子开始向外生长&#x…

音视频相关的一些基本概念

音视频相关的一些基本概念 文章目录 音视频相关的一些基本概念RTTH264profile & levelI帧 vs IDRMP4 封装格式AAC封装格式TS封装格式Reference RTT TCP中的RTT指的是“往返时延”&#xff08;Round-Trip Time&#xff09;&#xff0c;即从发送方发送数据开始&#xff0c;到…

春秋云境 CVE 复现

CVE-2022-4230 靶标介绍 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数&#xff0c;这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下&#xff0c;具有管理选项功能 (admin) 的用户可以使用受影响的功能&#xff0c;但是该插件有一个设置允许低权限用…

Linux—进程概念学习-03

目录 Linux—进程学习—31.进程优先级1.1Linux中的进程优先级1.2修改进程优先级—top 2.进程的其他概念3.进程切换4.环境变量4.0环境变量的理解4.1环境变量的基本概念4.2添加环境变量—export4.3Linux中环境变量的由来4.4常见环境变量4.5和环境变量相关的命令4.6通过系统调用获…

go语言逆向-基础basic

文章目录 go 编译命令 ldflags -w -s的作用和问题使用 file 命令查看文件类型 go 语言逆向参考go ID版本GOROOT和GOPATHGOROOTGOPATHGOROOT和GOPATH的关系示例 go build和 go modpclntab &#xff08;Program Counter Line Table 程序计数器行数映射表&#xff09;Moduledata程…

RAG架构类型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

PostgreSQL详细安装教程

#安装PostgreSQL的yum仓库 sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm#安装PostgreSQL 15版本 sudo yum install -y postgresql15-server#初始化数据库&#xff08;若要自定义数据库存储目录…

uniapp介入极光推送教程 超级详细

直接按照下面教程操作 一步一步来 很快就能 完成 下面的文章非常详细 &#xff0c;我就不班门弄斧了 直接上原文链接 https://blog.csdn.net/weixin_52830464/article/details/143823231

公司金融期末考试题目

公司金融期末考试题 选择题 1.现金折扣和信用条件&#xff08;教材P253&#xff09; 题目类似&#xff1a; 下列不属于信用条件的是&#xff08;&#xff09;。 现金折扣 数量折扣信用期限 折扣期限 给定的信用条件为"1/10&#xff0c;n/40"&#xff0c;则其含义…

图论入门编程

卡码网刷题链接&#xff1a;98. 所有可达路径 一、题目简述 二、编程demo 方法①邻接矩阵 from collections import defaultdict #简历邻接矩阵 def build_graph(): n, m map(int,input().split()) graph [[0 for _ in range(n1)] for _ in range(n1)]for _ in range(m): …