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. ST表(模板)「 查询区间最值 」

    The Water Problem HDU - 5443 「 第一部分nlogn预处理   第二部分O(1)询问 」 #include <iostream> #include <bi ...

  2. Fidller抓包分析post请求

    目的:抓包是为了最近做接口测试做准备,以前没有用过这个工具,最近来学下,但是网上很多文章了,所以不一一记录,有一部分参考即可 1.如何抓取想要的web端或者手机端包,已经有很多文章谢了,推荐的参考文章 ...

  3. [WEB安全]IIS-PUT漏洞

    目录 0x00 IIS简介 0x01 Put漏洞造成原因 0x02 实验环境搭建 0x03 需要用到的工具 0x04 IIS-PUT漏洞演示实战 0x05 常见请求协议 0x06 漏洞修复建议 0x0 ...

  4. 【2018.07.29】(深度优先搜索/回溯)学习DFS算法小记

    参考网站:https://blog.csdn.net/ldx19980108/article/details/76324307 这个网站里有动态图给我们体现BFS和DFS的区别:https://www ...

  5. c++比例-libcurl多线程并发时的core【转载】

    转自: https://www.cnblogs.com/edgeyang/articles/3722035.html 浅析libcurl多线程安全问题 背景:使用多线程libcurl发送请求,在未设置 ...

  6. Web安全测试 — 手工安全测试方法&修改建议

    常见问题 1.XSS(CrossSite Script)跨站脚本攻击 XSS(CrossSite Script)跨站脚本攻击.它指的是恶意攻击者往Web 页面里插入恶意 html代码,当用户浏览该页之 ...

  7. php手记之01-tp5框架安装

    1.1.介绍 在web领域,PHP是所有编程语言中比较受欢迎的一门语言! PHP已经诞生出几十种编程框架!但国内最热门和使用率最好的框架有Thinkphp和Laravel这两款PHP框架! 1.2.为 ...

  8. 深度学习: 学习率 (learning rate)

    Introduction 学习率 (learning rate),控制 模型的 学习进度 : lr 即 stride (步长) ,即反向传播算法中的 ηη : ωn←ωn−η∂L∂ωnωn←ωn−η∂ ...

  9. Linux中touch命令使用(创建文件)

    touch命令有两个功能: 1.用于把已存在文件的时间标签更新为系统当前的时间(默认方式),它们的数据将原封不动地保留下来: 2.用来创建新的空文件. 语法 touch(选项)(参数) 选项 -a:或 ...

  10. 【SpringBoot/MVC】从Oracle下载百万条记录的CSV

    工程下载地址:https://files.cnblogs.com/files/xiandedanteng/CsvDownloadOracle20191110-2.rar 画面: 核心代码: 控制器: ...