经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
redis 实现登陆次数限制
来源:cnblogs  作者:xiaoyureed  时间:2019/8/7 8:43:48  对本文有异议

title: redis-login-limitation

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

基本思路

比如希望达到的要求是这样: 在 1min 内登陆异常次数达到5次, 锁定该用户 1h

那么登陆请求的参数中, 会有一个参数唯一标识一个 user, 比如 邮箱/手机号/userName

用这个参数作为key存入redis, 对应的value为登陆错误的次数, string 类型, 并设置过期时间为 1min. 当获取到的 value == "4" , 说明当前请求为第 5 次登陆异常, 锁定.

所谓的锁定, 就是将对应的value设置为某个标识符, 比如"lock", 并设置过期时间为 1h

核心代码

定义一个注解, 用来标识需要登陆次数校验的方法

  1. package io.github.xiaoyureed.redispractice.anno;
  2. import java.lang.annotation.*;
  3. @Documented
  4. @Target({ElementType.METHOD})
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface RedisLimit {
  7. /**
  8. * 标识参数名, 必须是请求参数中的一个
  9. */
  10. String identifier();
  11. /**
  12. * 在多长时间内监控, 如希望在 60s 内尝试
  13. * 次数限制为5次, 那么 watch=60; unit: s
  14. */
  15. long watch();
  16. /**
  17. * 锁定时长, unit: s
  18. */
  19. long lock();
  20. /**
  21. * 错误的尝试次数
  22. */
  23. int times();
  24. }

编写切面, 在目标方法前后进行校验, 处理...

  1. package io.github.xiaoyureed.redispractice.aop;
  2. @Component
  3. @Aspect
  4. // Ensure that current advice is outer compared with ControllerAOP
  5. // so we can handling login limitation Exception in this aop advice.
  6. //@Order(9)
  7. @Slf4j
  8. public class RedisLimitAOP {
  9. @Autowired
  10. private StringRedisTemplate stringRedisTemplate;
  11. @Around("@annotation(io.github.xiaoyureed.redispractice.anno.RedisLimit)")
  12. public Object handleLimit(ProceedingJoinPoint joinPoint) {
  13. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  14. final Method method = methodSignature.getMethod();
  15. final RedisLimit redisLimitAnno = method.getAnnotation(RedisLimit.class);// 貌似可以直接在方法参数中注入 todo
  16. final String identifier = redisLimitAnno.identifier();
  17. final long watch = redisLimitAnno.watch();
  18. final int times = redisLimitAnno.times();
  19. final long lock = redisLimitAnno.lock();
  20. // final ServletRequestAttributes att = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
  21. // final HttpServletRequest request = att.getRequest();
  22. // final String identifierValue = request.getParameter(identifier);
  23. String identifierValue = null;
  24. try {
  25. final Object arg = joinPoint.getArgs()[0];
  26. final Field declaredField = arg.getClass().getDeclaredField(identifier);
  27. declaredField.setAccessible(true);
  28. identifierValue = (String) declaredField.get(arg);
  29. } catch (NoSuchFieldException e) {
  30. log.error(">>> invalid identifier [{}], cannot find this field in request params", identifier);
  31. } catch (IllegalAccessException e) {
  32. e.printStackTrace();
  33. }
  34. if (StringUtils.isBlank(identifierValue)) {
  35. log.error(">>> the value of RedisLimit.identifier cannot be blank, invalid identifier: {}", identifier);
  36. }
  37. // check User locked
  38. final ValueOperations<String, String> ssOps = stringRedisTemplate.opsForValue();
  39. final String flag = ssOps.get(identifierValue);
  40. if (flag != null && "lock".contentEquals(flag)) {
  41. final BaseResp result = new BaseResp();
  42. result.setErrMsg("user locked");
  43. result.setCode("1");
  44. return new ResponseEntity<>(result, HttpStatus.OK);
  45. }
  46. ResponseEntity result;
  47. try {
  48. result = (ResponseEntity) joinPoint.proceed();
  49. } catch (Throwable e) {
  50. result = handleLoginException(e, identifierValue, watch, times, lock);
  51. }
  52. return result;
  53. }
  54. private ResponseEntity handleLoginException(Throwable e, String identifierValue, long watch, int times, long lock) {
  55. final BaseResp result = new BaseResp();
  56. result.setCode("1");
  57. if (e instanceof LoginException) {
  58. log.info(">>> handle login exception...");
  59. final ValueOperations<String, String> ssOps = stringRedisTemplate.opsForValue();
  60. Boolean exist = stringRedisTemplate.hasKey(identifierValue);
  61. // key doesn't exist, so it is the first login failure
  62. if (exist == null || !exist) {
  63. ssOps.set(identifierValue, "1", watch, TimeUnit.SECONDS);
  64. result.setErrMsg(e.getMessage());
  65. return new ResponseEntity<>(result, HttpStatus.OK);
  66. }
  67. String count = ssOps.get(identifierValue);
  68. // has been reached the limitation
  69. if (Integer.parseInt(count) + 1 == times) {
  70. log.info(">>> [{}] has been reached the limitation and will be locked for {}s", identifierValue, lock);
  71. ssOps.set(identifierValue, "lock", lock, TimeUnit.SECONDS);
  72. result.setErrMsg("user locked");
  73. return new ResponseEntity<>(result, HttpStatus.OK);
  74. }
  75. ssOps.increment(identifierValue);
  76. result.setErrMsg(e.getMessage() + "; you have try " + ssOps.get(identifierValue) + "times.");
  77. }
  78. log.error(">>> RedisLimitAOP cannot handle {}", e.getClass().getName());
  79. return new ResponseEntity<>(result, HttpStatus.OK);
  80. }
  81. }

这样使用:

  1. package io.github.xiaoyureed.redispractice.web;
  2. @RestController
  3. public class SessionResources {
  4. @Autowired
  5. private SessionService sessionService;
  6. /**
  7. * 1 min 之内尝试超过5次, 锁定 user 1h
  8. */
  9. @RedisLimit(identifier = "name", watch = 30, times = 5, lock = 10)
  10. @RequestMapping(value = "/session", method = RequestMethod.POST)
  11. public ResponseEntity<LoginResp> login(@Validated @RequestBody LoginReq req) {
  12. return new ResponseEntity<>(sessionService.login(req), HttpStatus.OK);
  13. }
  14. }

references

https://github.com/xiaoyureed/redis-login-limitation

原文链接:http://www.cnblogs.com/xiaoyuhah/p/11306154.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号