Spring项目使用Redis限制用户登录失败的次数以及暂时锁定用户登录权限

文章目录

    • 背景
    • 环境
    • 代码实现
      • 0. 项目结构图(供参考)
      • 1. 数据库中的表(供参考)
      • 2. 依赖(pom.xml)
      • 3. 配置文件(application.yml)
      • 4. 配置文件(application-dev.yml)
      • 5. UserLoginDTO
      • 6. DemoConstant
      • 7. User(后来才用的lombok,没有统一写法)
      • 8. UserMapper
      • 9. UserService(供参考)
      • 10. UserController
      • 11. RedisConfig
      • 12. 统一返回结果
      • 13. 启动类LoginDemoApplication
    • 测试登录接口(一分钟内五次密码全错,触发账号锁定登录权限)
      • 第一次测试(300ms)
      • 第二次测试(32ms)
      • 第三次测试(22ms)
      • 第四次测试(12ms)
      • 第五次测试(8ms)
    • 总结

背景

前两天被面试到这个问题,最初回答的不是很合理,登录次数这方面记录还一直往数据库上面想,后来感觉在数据库中加一个登录日志表,来查询一段时间内用户登录的次数,现在看来,自己还是太年轻了,做个登录限制肯定是为了防止数据库高并发,加一个表来记录登录日志,这就把请求都打到数据库了,后来看了前辈们的解决方案,可以用Redis来实现,包括登录次数和账号锁定,我最开始在回答这个问题的时候只回答到了账号锁定用Redis做管理,前者登录次数回答的比较差劲,后来我也及时复盘了这次面试,就标题的内容做出基本实现

环境

Java8、MySQL8、Redis、IDEA,环境支持的同学可以参考这份代码实现以下

代码实现

0. 项目结构图(供参考)

在这里插入图片描述

1. 数据库中的表(供参考)

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for login_demo_user
-- ----------------------------
DROP TABLE IF EXISTS `login_demo_user`;
CREATE TABLE `login_demo_user`  (`id` bigint NOT NULL COMMENT '主键',`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',PRIMARY KEY (`id`, `username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of login_demo_user
-- ----------------------------
INSERT INTO `login_demo_user` VALUES (1, 'hh', 'hh');SET FOREIGN_KEY_CHECKS = 1;

2. 依赖(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/></parent><groupId>com.openallzzz</groupId><artifactId>logindemo</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

3. 配置文件(application.yml)

server:port: 8080spring:profiles:active: devmain:allow-circular-references: truedatasource:druid:driver-class-name: ${datasource.driver-class-name}url: jdbc:mysql://${datasource.host}:${datasource.port}/${datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: ${datasource.username}password: ${datasource.password}redis:host: ${redis.host}port: ${redis.port}database: ${redis.database}mybatis:#mapper配置文件mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.openallzzz.logindemo.entityconfiguration:#开启驼峰命名map-underscore-to-camel-case: truelogging:level:com:openallzzz:mapper: debugservice: infocontroller: info

4. 配置文件(application-dev.yml)

datasource:driver-class-name: com.mysql.cj.jdbc.Driverhost: localhostport: 3306database: testdbusername: rootpassword: root
redis:host: localhostport: 6379database: 1

5. UserLoginDTO

package com.openallzzz.logindemo.dto;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginDTO {private String username;private String password;}

6. DemoConstant

package com.openallzzz.logindemo.constant;public class DemoConstant {// 每分钟限制登录的最大次数public static final int MAX_LOGIN_TIMRS_PER_MINUTE = 5;
}

7. User(后来才用的lombok,没有统一写法)

package com.openallzzz.logindemo.entity;public class User {private Long id;private String username;private String password;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

8. UserMapper

package com.openallzzz.logindemo.mapper;import com.openallzzz.logindemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface UserMapper {@Select("select id, username, password from login_demo_user where username = #{username}")User selectByUsername(String username);}

9. UserService(供参考)

package com.openallzzz.logindemo.service;import com.openallzzz.logindemo.constant.DemoConstant;
import com.openallzzz.logindemo.dto.UserLoginDTO;
import com.openallzzz.logindemo.entity.User;
import com.openallzzz.logindemo.mapper.UserMapper;
import com.openallzzz.logindemo.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisTemplate redisTemplate;public Result<String> login(UserLoginDTO userLoginDTO) {if (userLoginDTO == null || userLoginDTO.getUsername() == null || userLoginDTO.getPassword() == null) {return Result.error("用户信息不完整!");}String username = userLoginDTO.getUsername();String password = userLoginDTO.getPassword();boolean lockStatus = getLockStatus(username);if (lockStatus) {return Result.error("失败次数过多,请稍后重试");}User user = userMapper.selectByUsername(username);if (user.getPassword().equals(password)) {clearLastCount(username);clearLockStatus(username);return Result.success();} else {int lastCount = getLastCount(username);if (lastCount + 1 == DemoConstant.MAX_LOGIN_TIMRS_PER_MINUTE) {setLockStatus(username);return Result.error("失败次数过多,请稍后重试");} else {setLastCount(username);return Result.error("登录失败,请检查用户名或密码,再重试");}}}private void clearLockStatus(String username) {log.info("清除用户锁定状态:{}", username);String key = "Lock" + ":" + username;redisTemplate.delete(key);}private void clearLastCount(String username) {log.info("将用户登录次数还原:{}", username);String key = "Count" + ":" + username;redisTemplate.delete(key);}private int getLastCount(String username) {log.info("获取用户一分钟内已经失败登录了多少次:{}", username);String key = "Count" + ":" + username;Integer count = (Integer) redisTemplate.opsForValue().get(key);return count == null ? 0 : count;}private void setLastCount(String username) {log.info("设置用户一分钟内已经失败登录了多少次:{}", username);String key = "Count" + ":" + username;redisTemplate.opsForValue().set(key, getLastCount(username) + 1, 1, TimeUnit.MINUTES);}private void setLockStatus(String username) {log.info("锁定用户,限制其登录:{}", username);String key = "Lock" + ":" + username;redisTemplate.opsForValue().set(key, "lock", 2, TimeUnit.HOURS);}private boolean getLockStatus(String username) {log.info("获取用户是否被限制其登录:{}", username);String key = "Lock" + ":" + username;String o = (String) redisTemplate.opsForValue().get(key);if ("lock".equals(o)) return true;return false;}}

10. UserController

package com.openallzzz.logindemo.controller;import com.openallzzz.logindemo.dto.UserLoginDTO;
import com.openallzzz.logindemo.result.Result;
import com.openallzzz.logindemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/login")public Result<String> login(@RequestBody UserLoginDTO userLoginDTO) {log.info("用户登录:{}", userLoginDTO);return userService.login(userLoginDTO);}}

11. RedisConfig

package com.openallzzz.logindemo.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
@EnableCaching
public class RedisConfig {@Bean(name = "redisTemplate")public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> template = new RedisTemplate<>();//配置连接工厂template.setConnectionFactory(factory);//使用jackson序列化和反序列value的值,Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();//指定需要序列化的范围,All表示field、get和set,以及修饰符范围,ANY表示所有范围,包括privatemapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);jacksonSerializer.setObjectMapper(mapper);//设置template中value使用Jackson2JsonRedisSerializer序列化template.setValueSerializer(jacksonSerializer);//设置template中key使用StringRedisSerializer序列化template.setKeySerializer(new StringRedisSerializer());//这是hash中key和value的序列化方式,key采用StringRedisSerializer,value采用Jackson2JsonRedisSerializertemplate.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(jacksonSerializer);//使template属性生效template.afterPropertiesSet();return template;}
}

12. 统一返回结果

package com.openallzzz.logindemo.result;import lombok.Data;import java.io.Serializable;/*** 后端统一返回结果* @param <T>*/
@Data
public class Result<T> implements Serializable {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据public static <T> Result<T> success() {Result<T> result = new Result<T>();result.code = 1;return result;}public static <T> Result<T> success(T object) {Result<T> result = new Result<T>();result.data = object;result.code = 1;return result;}public static <T> Result<T> error(String msg) {Result result = new Result();result.msg = msg;result.code = 0;return result;}}

13. 启动类LoginDemoApplication

package com.openallzzz.logindemo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class LoginDemoApplication {public static void main(String[] args) {SpringApplication.run(LoginDemoApplication.class, args);}}

测试登录接口(一分钟内五次密码全错,触发账号锁定登录权限)

第一次测试(300ms)

在这里插入图片描述

第二次测试(32ms)

在这里插入图片描述

第三次测试(22ms)

在这里插入图片描述

第四次测试(12ms)

在这里插入图片描述

第五次测试(8ms)

在这里插入图片描述

总结

登录业务预先判断了该账号是否被锁定,如果短期内有大量登录请求(用户不断试错、被恶意攻击),压力只会给到Redis,从而避免DB被大量请求打中。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kafka--kafka基础概念-ISR详解

kafka基础概念-ISR详解 主要是讲 主 往 从同步中的问题 当绿色P1接收到写入的数据&#xff0c;要同步到紫色的P1S1和P1S2 如何保证一致性呢&#xff1f; 使用In Sync Replicas 也就是ISR概念 为什么不一致的&#xff1f; 因为P1S1同步数据 可能花费 50ms P1S2可能花费60ms…

【项目实践】基于LSTM的一维数据扩展与预测

基于LSTM的一维数据拟合扩展 一、引(fei)言(hua) 我在做Sri Lanka生态系统服务价值计算时&#xff0c;中间遇到了一点小问题。从世界粮农组织(FAO)上获得Sri Lanka主要农作物产量和价格数据时&#xff0c;其中的主要作物Sorghum仅有2001-2006年的数据&#xff0c;而Millet只有…

hive-无法启动hiveserver2

启动hiveserver2没有反应&#xff0c;客户端也无法连接( beeline -u jdbc:hive2://node01:10000 -n root) 报错如下 查看hive的Log日志&#xff0c;发现如下报错 如何解决 在hive的hive_site.xml中添加如下代码 <property><name>hive.server2.active.passive…

基于Python的微博大数据舆情分析,舆论情感分析可视化系统,可作为Python毕业设计

运行效果图 基于Python的微博大数据舆情分析&#xff0c;舆论情感分析可视化系统 系统介绍 微博舆情分析系统&#xff0c;项目后端分爬虫模块、数据分析模块、数据存储模块、业务逻辑模块组成。 先后进行了数据获取和筛选存储&#xff0c;对存储后的数据库数据进行提取分析处…

Python可视化在量化交易中的应用(14)_Seaborn散点图

Seaborn中带回归线的散点图的绘制方法 seaborn中绘制散点图使用的是sns.scatterplot()函数&#xff1a; sns.scatterplot(x,y,hue,style,size,data,palette,hue_order,hue_norm,sizes,size_order,size_norm,markers,style_order,x_bins,y_bins,units,estimator,ci95,n_boot100…

Spring源码编译-for mac

超详细的spring源码编译 记&#xff1a;编译成功时间&#xff1a;2023.08.19 环境准备&#xff1a; 1.idea 2023.1.1 Community Edition 2.jdk1.8 3.gradlegradle-5.6.4 4.spring源码(版本&#xff1a;spring-framework-v5.2.25.RELEASE) 一.spring源码下载 github 加速网站&…

PyQt5登录界面跳转

目录 1、设计ui界面 2、设计逻辑代码&#xff0c;实现登录界面跳转 3、结果 1、设计ui界面 设计后的ui界面 在这里可以设置密码不显示 这里可以设置快捷键 最后将ui界面转为py文件后获得的逻辑代码为&#xff1a;&#xff08;文件名为Login.py&#xff09; # -*- coding: u…

Linux学习之Telnet明文漏洞

yum install telnet telnet-server xinetd -y安装软件。 systemctl start xinetd.service开启xinetd&#xff0c;systemctl start telnet.socket开启telnet。 xinetd来监控端口&#xff0c;然后把数据传给telnet。 ifconfig eth0看一下eth0网卡信息&#xff0c;。 iptable…

SpringBoot统⼀功能处理

前言&#x1f36d; ❤️❤️❤️SSM专栏更新中&#xff0c;各位大佬觉得写得不错&#xff0c;支持一下&#xff0c;感谢了&#xff01;❤️❤️❤️ Spring Spring MVC MyBatis_冷兮雪的博客-CSDN博客 本章是讲Spring Boot 统⼀功能处理模块&#xff0c;也是 AOP 的实战环节&…

226、仿真-基于51单片机楼道教室走道智能灯光光照人体感应检测控制Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&…

基于docker搭建pytest自动化测试环境(docker+pytest+jenkins+allure)

pytest搭建自动化测试环境&#xff08;dockerpytestjenkinsallure&#xff09; 这里我以ubuntu18为例 如果有docker环境&#xff0c;可以直接拉取我打包好的镜像docker pull ziyigun/jenkins:v1.0 1 搭建Docker 1.1 安装docker # 配置docker安装环境 sudo apt-get install ap…

Linux 系统编程拾遗

Linux 系统编程拾遗 进程的创建 进程的创建 fork()、exit()、wait()以及execve()的简介 创建新进程&#xff1a;fork()