title: redis-login-limitation

利用 redis 实现登陆次数限制, 注解 + aop, 核心代码很简单.

基本思路

比如希望达到的要求是这样: 在 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 实现登陆次数限制的更多相关文章

  1. 【BASIS系列】SAP 中查看account登陆次数及时间的情况

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[BASIS系列]SAP 中查看account登 ...

  2. Redis解决“重试次数”场景的实现思路

    很多地方都要用到重试次数限制,不然就会被暴力破解.比如登录密码. 下面不是完整代码,只是伪代码,提供一个思路. 第一种(先声明,这样写有个bug) import java.text.MessageFo ...

  3. 【Redis使用系列】redis设置登陆密码

    找到安装redis的配置文件,找到redis.comf文件找到#requirepass foobared 新建一行 requirepass  xxxx 你的密码 ,然后重启.再登录的时候可以登录,但是 ...

  4. shiro 错误登陆次数限制

    第一步:在spring-shiro.xml 中配置缓存管理器和认证匹配器 <!-- 缓存管理器 使用Ehcache实现 --><bean id="cacheManager& ...

  5. 帝国empirecms后台登陆次数限制修改

    打开文件:\e\config\config.php, 找到 'loginnum'=>5, 把5改为自己想要的数字即可

  6. Servlet学习(三)——实例:用户登录并记录登陆次数

    1.前提:在Mysql数据库下建立数据库web13,在web13下创建一张表user,插入几条数据如下: 2.创建HTML文件,命名为login,作为登录界面(以post方式提交) <!DOCT ...

  7. Redis & Python/Django 简单用户登陆

    一.Redis key相关操作: 1.del key [key..] 删除一个或多个key,如果不存在则忽略 2.keys pattern keys模式匹配,符合glob风格通配符,glob风格的通配 ...

  8. Redis+Django(Session,Cookie)的用户系统

    一.Django authentication django authentication提供了一个便利的user api接口,无论在py中 request.user,参见Request and re ...

  9. Redis+Django(Session,Cookie、Cache)的用户系统

    转自 http://www.cnblogs.com/BeginMan/p/3890761.html 一.Django authentication django authentication 提供了一 ...

随机推荐

  1. 国外最受欢迎的BT-磁力网站

    1.海盗湾 The Pirate Bay 2.KickAssTorrents 3.Torrentz 4.zooqle 5.SumoTorrent 6.TorrentDownloads 7.Rarbg ...

  2. 使用u盘安装linux(manjaro)时Grub报错

    本文通过MetaWeblog自动发布,原文及更新链接:https://extendswind.top/posts/technical/manjaro_install_problem_grub 错误 e ...

  3. P3986 斐波那契数列——数学(EXGCD)

    https://www.luogu.org/problem/P3986 很久很久以前,我好像写过exgcd,但是我已经忘了: 洛谷上搜EXGCD搜不到,要搜(扩展欧几里得) 这道题就是ax+by=k, ...

  4. 【转载】Hadoop集群各部分常用端口号

    hadoop集群的各部分一般都会使用到多个端口,有些是daemon之间进行交互之用,有些是用于RPC访问以及HTTP访问.而随着hadoop周边组件的增多,完全记不住哪个端口对应哪个应用,特收集记录如 ...

  5. Jenkins中插件下载失败的解决办法

    插件下载失败原因:通过国外服务器下载镜像,有较高的失败率,某些插件下载失败或者中断会引起其他有依赖关系的插件也下载失败 解决方案:1. 使用VPN.2. Jenkins镜像地址改为国内镜像地址:系统管 ...

  6. Lucene核心数据结构——FST存词典,跳表存倒排或者roarning bitmap 见另外一个文章

    Lucene实现倒排表没有使用bitmap,为了效率,lucene使用了一些策略,具体如下:1. 使用FST保存词典,FST可以实现快速的Seek,这种结构在当查询可以表达成自动机时(PrefixQu ...

  7. POX flow_stats2.py analysis by uestc_gremount

    该程序是POX WIKI上的程序,我只是将统计的报文修改了以下,并做了对这个程序运行流程的分析: 1.程序从launch开始运行 2.监听2个事件,如果监听到"FlowStatsReceiv ...

  8. 阿里巴巴高可用技术专家襄玲:压测环境的设计和搭建 PTS - 襄玲 云栖社区 今天

    阿里巴巴高可用技术专家襄玲:压测环境的设计和搭建 PTS - 襄玲 云栖社区 今天

  9. SQL-W3School-函数:SQL HAVING 子句

    ylbtech-SQL-W3School-函数:SQL HAVING 子句 1.返回顶部 1. HAVING 子句 在 SQL 中增加 HAVING 子句原因是,WHERE 关键字无法与合计函数一起使 ...

  10. Tomcat日志监控工具——Probe

    今天遇到项目运行过程中需要查看用户访问日志,log4j.properties配置好,将log日志输出到tomcat的log文件夹下,但不可能每次都去服务器上拉取log文件查看,网上找了下,发现一个日志 ...