redis 实现登陆次数限制
title: redis-login-limitation
基本思路
比如希望达到的要求是这样: 在 1min 内登陆异常次数达到5次, 锁定该用户 1h
那么登陆请求的参数中, 会有一个参数唯一标识一个 user, 比如 邮箱/手机号/userName
用这个参数作为key存入redis, 对应的value为登陆错误的次数, string 类型, 并设置过期时间为 1min. 当获取到的 value == "4" , 说明当前请求为第 5 次登陆异常, 锁定.
所谓的锁定, 就是将对应的value设置为某个标识符, 比如"lock", 并设置过期时间为 1h
核心代码
定义一个注解, 用来标识需要登陆次数校验的方法
package io.github.xiaoyureed.redispractice.anno;
import java.lang.annotation.*;
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLimit {
/**
* 标识参数名, 必须是请求参数中的一个
*/
String identifier();
/**
* 在多长时间内监控, 如希望在 60s 内尝试
* 次数限制为5次, 那么 watch=60; unit: s
*/
long watch();
/**
* 锁定时长, unit: s
*/
long lock();
/**
* 错误的尝试次数
*/
int times();
}
编写切面, 在目标方法前后进行校验, 处理...
package io.github.xiaoyureed.redispractice.aop;
@Component
@Aspect
// Ensure that current advice is outer compared with ControllerAOP
// so we can handling login limitation Exception in this aop advice.
//@Order(9)
@Slf4j
public class RedisLimitAOP {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("@annotation(io.github.xiaoyureed.redispractice.anno.RedisLimit)")
public Object handleLimit(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
final Method method = methodSignature.getMethod();
final RedisLimit redisLimitAnno = method.getAnnotation(RedisLimit.class);// 貌似可以直接在方法参数中注入 todo
final String identifier = redisLimitAnno.identifier();
final long watch = redisLimitAnno.watch();
final int times = redisLimitAnno.times();
final long lock = redisLimitAnno.lock();
// final ServletRequestAttributes att = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
// final HttpServletRequest request = att.getRequest();
// final String identifierValue = request.getParameter(identifier);
String identifierValue = null;
try {
final Object arg = joinPoint.getArgs()[0];
final Field declaredField = arg.getClass().getDeclaredField(identifier);
declaredField.setAccessible(true);
identifierValue = (String) declaredField.get(arg);
} catch (NoSuchFieldException e) {
log.error(">>> invalid identifier [{}], cannot find this field in request params", identifier);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (StringUtils.isBlank(identifierValue)) {
log.error(">>> the value of RedisLimit.identifier cannot be blank, invalid identifier: {}", identifier);
}
// check User locked
final ValueOperations<String, String> ssOps = stringRedisTemplate.opsForValue();
final String flag = ssOps.get(identifierValue);
if (flag != null && "lock".contentEquals(flag)) {
final BaseResp result = new BaseResp();
result.setErrMsg("user locked");
result.setCode("1");
return new ResponseEntity<>(result, HttpStatus.OK);
}
ResponseEntity result;
try {
result = (ResponseEntity) joinPoint.proceed();
} catch (Throwable e) {
result = handleLoginException(e, identifierValue, watch, times, lock);
}
return result;
}
private ResponseEntity handleLoginException(Throwable e, String identifierValue, long watch, int times, long lock) {
final BaseResp result = new BaseResp();
result.setCode("1");
if (e instanceof LoginException) {
log.info(">>> handle login exception...");
final ValueOperations<String, String> ssOps = stringRedisTemplate.opsForValue();
Boolean exist = stringRedisTemplate.hasKey(identifierValue);
// key doesn't exist, so it is the first login failure
if (exist == null || !exist) {
ssOps.set(identifierValue, "1", watch, TimeUnit.SECONDS);
result.setErrMsg(e.getMessage());
return new ResponseEntity<>(result, HttpStatus.OK);
}
String count = ssOps.get(identifierValue);
// has been reached the limitation
if (Integer.parseInt(count) + 1 == times) {
log.info(">>> [{}] has been reached the limitation and will be locked for {}s", identifierValue, lock);
ssOps.set(identifierValue, "lock", lock, TimeUnit.SECONDS);
result.setErrMsg("user locked");
return new ResponseEntity<>(result, HttpStatus.OK);
}
ssOps.increment(identifierValue);
result.setErrMsg(e.getMessage() + "; you have try " + ssOps.get(identifierValue) + "times.");
}
log.error(">>> RedisLimitAOP cannot handle {}", e.getClass().getName());
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
这样使用:
package io.github.xiaoyureed.redispractice.web;
@RestController
public class SessionResources {
@Autowired
private SessionService sessionService;
/**
* 1 min 之内尝试超过5次, 锁定 user 1h
*/
@RedisLimit(identifier = "name", watch = 30, times = 5, lock = 10)
@RequestMapping(value = "/session", method = RequestMethod.POST)
public ResponseEntity<LoginResp> login(@Validated @RequestBody LoginReq req) {
return new ResponseEntity<>(sessionService.login(req), HttpStatus.OK);
}
}
references
https://github.com/xiaoyureed/redis-login-limitation
redis 实现登陆次数限制的更多相关文章
- 【BASIS系列】SAP 中查看account登陆次数及时间的情况
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[BASIS系列]SAP 中查看account登 ...
- Redis解决“重试次数”场景的实现思路
很多地方都要用到重试次数限制,不然就会被暴力破解.比如登录密码. 下面不是完整代码,只是伪代码,提供一个思路. 第一种(先声明,这样写有个bug) import java.text.MessageFo ...
- 【Redis使用系列】redis设置登陆密码
找到安装redis的配置文件,找到redis.comf文件找到#requirepass foobared 新建一行 requirepass xxxx 你的密码 ,然后重启.再登录的时候可以登录,但是 ...
- shiro 错误登陆次数限制
第一步:在spring-shiro.xml 中配置缓存管理器和认证匹配器 <!-- 缓存管理器 使用Ehcache实现 --><bean id="cacheManager& ...
- 帝国empirecms后台登陆次数限制修改
打开文件:\e\config\config.php, 找到 'loginnum'=>5, 把5改为自己想要的数字即可
- Servlet学习(三)——实例:用户登录并记录登陆次数
1.前提:在Mysql数据库下建立数据库web13,在web13下创建一张表user,插入几条数据如下: 2.创建HTML文件,命名为login,作为登录界面(以post方式提交) <!DOCT ...
- Redis & Python/Django 简单用户登陆
一.Redis key相关操作: 1.del key [key..] 删除一个或多个key,如果不存在则忽略 2.keys pattern keys模式匹配,符合glob风格通配符,glob风格的通配 ...
- Redis+Django(Session,Cookie)的用户系统
一.Django authentication django authentication提供了一个便利的user api接口,无论在py中 request.user,参见Request and re ...
- Redis+Django(Session,Cookie、Cache)的用户系统
转自 http://www.cnblogs.com/BeginMan/p/3890761.html 一.Django authentication django authentication 提供了一 ...
随机推荐
- linux系列(二十):find命令
1.命令格式 find pathname -options [-print -exec -ok ...] 2.命令功能 用于在文件树种查找文件,并作出相应的处理 3.命令参数 pathname: fi ...
- $('#jyzjg').combobox('clear');
$('#jyzjg').combobox('clear'); alert($('#jyzjg').combobox("getValue" ...
- 20175234 2018-2019-2 《Java程序设计》第十周学习总结
目录 20175234 2018-2019-2 <Java程序设计>第十周学习总结 教材学习内容总结 12.1进程与线程 12.2 Java中的线程 12.3 Thread类与线程的创建 ...
- nessus在Linux上的安装
Nessus有三种安装方式 1.源文件安装 源文件安装是最复杂的安装方式,用此方式安装可以修改配置参数. 2.rpm安装 rpm安装比起源文件安装更简单一些,它已经把一些底层的东西写好了,用户只要按步 ...
- 接口操作XML
接口操作XML 以下代码旨在 脱离TXMLDocument 操作 xml. unit Unit3; interface uses Windows, Messages, SysUtils, Varian ...
- daoliu平台:测试线路图
1.进行需求收集及分析 搜索关键字:导流平台 同类产品体验及熟悉 搞清楚,业务流.数据流.用户焦点 2.初步熟悉原型 初步熟悉.遍历原型, 初步进行测试需求分析 3.聆听及和权威的人进行沟通 测试人员 ...
- 线程池 | Java多线程,彻底搞懂线程池
熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了. 最近看了一些相关文章,并亲自研究了一下源码,发现有些文章还是有些问题的,所以我也总结了 ...
- linux内核中的regmap是如何初始化的?
1. 内核版本 5.2.0 2. 请看devm_regmap_init_i2c (include/linux/regmap.h) /** * devm_regmap_init_i2c() - Init ...
- PHP判断是否在微信内部浏览器访问
<?php if(is_weixin()){ echo "这是微信内部浏览器"; }else{ echo "这是微信外部浏览器"; } function ...
- 【PHP】解决数据库查询出来的中文内容显示为问号“??”
方法一:在数据库连接后执行: mysql_query('SET NAMES utf8'); 代码: $dbconn=mysql_connect("localhost", " ...