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. 3、spark Wordcount

    一.用Java开发wordcount程序 1.开发环境JDK1.6 1.1 配置maven环境 1.2 如何进行本地测试 1.3 如何使用spark-submit提交到spark集群进行执行(spar ...

  2. Sprite Atlas与Sprite Mask详解

    https://www.sohu.com/a/169409304_280780 Unity 2017.1正式发布后,带来了一批能帮助大家更加简化工作流的新功能.今天这篇文章,将由Unity技术经理成亮 ...

  3. @Autowired 与@Resource的区别详解

    spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@PostConstruct以及@PreDestroy. @Resour ...

  4. 数据结构实验之图论七:驴友计划【迪杰斯特拉算法】(SDUT 3363)

    分析:可以求简单的任意两点间最短距离的稍微变形,一个板子题.  #include <iostream> #include <bits/stdc++.h> using names ...

  5. 用Python实现自己下载音乐的统计

    今天看Python实例,学习了如何对文件进行操作,突然想把自己网易云音乐下载到本地的歌曲名单写到一个txt中,看看具体情况.当然,我现在肯定无法做到直接去网易云音乐上爬取,就做个最简单的吧,嘻嘻^-^ ...

  6. 基于JSON的接口测试框架

    更多学习资料请加QQ群: 822601020获取 实现效果 需求场景: 公司微服务接口使用数字签名的方式, 使用Postman调试接口每次都需要修改源码将验签临时关闭, 但是关闭后,其他微服务不能正常 ...

  7. [APIO2018]铁人两项——圆方树+树形DP

    题目链接: [APIO2018]铁人两项 对于点双连通分量有一个性质:在同一个点双里的三个点$a,b,c$,一定存在一条从$a$到$c$的路径经过$b$且经过的点只被经过一次. 那么我们建出原图的圆方 ...

  8. vue tab嵌入iframe切换不刷新,相对完整的方案

    说到Vue的简单.便捷.高效,谁用谁喜欢,自然企业应用也来玩一把,三大经典组件:树控件,网格控件,选项卡控件: 本章先说选项卡tab控件的嵌入iframe. 本次主要解决以下问题: 1.tab控件混合 ...

  9. JVM的内存配置参数

    JVM的结构问题:JVM分两块:PermanentSapce和HeapSpace, HeapSpace = [old + new{=Eden,from,to}] PermantSpace主要负责存放加 ...

  10. golang通过ssh实现远程文件传输

    使用ssh远程操作文件, 主要是创建ssh, 直接上代码 import ( "fmt" "github.com/pkg/sftp" "golang.o ...