背景

在Web系统或一些Client端登录时,如果用户输错用户名或密码达到一定次数,一般会锁定账号或IP,本文只对IP锁定一段时间,不依赖于Redis这类中间件,使用纯Java实现锁定IP的功能,直接上代码。

代码篇

LockBean用于封装锁定IP信息,特意添加了isLockLogged,防止日志打印频繁

public class LockBean {//锁定时间private LocalDateTime lockDateTime;//失败次数private AtomicInteger failedCount;//是否被锁定private boolean isLocked;//首次验证失败时间戳private Long firstFailTime;//用于抑制日志打印,判断是否打印过被锁日志private boolean isLockLogged;public LockBean() {this.failedCount = new AtomicInteger(0);this.isLocked = false;this.firstFailTime = System.currentTimeMillis();}public LocalDateTime getLockDateTime() {return lockDateTime;}public void setLockDateTime(LocalDateTime lockDateTime) {this.lockDateTime = lockDateTime;}public int getFailedCount() {return failedCount.intValue();}/*** 添加失败次数*/public int incrementAndGetFailedCount() {return failedCount.incrementAndGet();}public boolean isLocked() {return isLocked;}public void setLocked(boolean locked) {this.isLocked = locked;}public void lock() {lockDateTime = LocalDateTime.now();isLocked = true;}public Long getFirstFailTime() {return firstFailTime;}public void setFirstFailTime(Long firstFailTime) {this.firstFailTime = firstFailTime;}public boolean isLockLogged() {return isLockLogged;}public void setLockLogged(boolean lockLogged) {isLockLogged = lockLogged;}
}

LockManager管理类负责对IP锁定和释放。默认校验周期10分钟内访问失败达到3次后,锁定IP30分钟。IP锁定储存在ConcurrentHashMap中,key为ip,value为LockBean。后台开一个定时ScheduledExecutorService,每分钟去清理超过锁定时长的ip和超过校验周期且未锁定的ip。

public class LockManager {private static final Logger LOGGER = LogManager.getLogger(LockManager.class);private volatile static LockManager instance = null;// 是否开启访问限制private boolean lockOption = true;// 最大访问失败次数 默认3次private int maxFailedCount = 3;// 访问校验周期 默认10minprivate Long verifyTimeUint = 600000L;// ip限制时长 默认30minprivate Long lockTime = 1800000L;// 锁定ip缓存private static Map<String, LockBean> lockMap = new ConcurrentHashMap<>();// 定时任务线程池private final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);// 定时任务开启状态private volatile boolean startStatus = false;// 锁对象private Object lock = new Object();private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");private LockManager() {init();}/*** 获取单例** @return*/public static LockManager getInstance() {if (instance == null) {synchronized (LockManager.class) {if (instance == null) {instance = new LockManager();}}}return instance;}private void init() {lockOption = Boolean.parseBoolean(System.getProperty("com.dzj.lockOption", "true"));if (lockOption) {try {maxFailedCount = Integer.parseInt(System.getProperty("com.dzj.maxFailedCount", "3"));} catch (NumberFormatException e) {LOGGER.error("[com.dzj.maxFailedCount] is error,use default config.");maxFailedCount = 3;}try {verifyTimeUint =Long.parseLong(System.getProperty("com.dzj.verifyTimeUint", "600000"));} catch (NumberFormatException e) {LOGGER.error("[com.dzj.maxFailedCount] is error,use default config.");verifyTimeUint = 600000L;}try {lockTime = Long.parseLong(System.getProperty("com.dzj.lockTime", "1800000"));} catch (NumberFormatException e) {LOGGER.error("[com.dzj.lockTime] is error,use default config.");lockTime = 1800000L;}}}/*** 开启定时任务,清理超过锁定时长ip和超过校验周期且未锁定ip*/public void start() {if (startStatus) {return;}synchronized (lock) {if (startStatus) {return;}this.scheduledExecutorService.scheduleAtFixedRate(() -> {if (!lockMap.isEmpty()) {cleanIpOverLockTime();cleanIpOverVerifyUnit();}}, 0, 1, TimeUnit.MINUTES);startStatus = true;}}/*** 清除超过锁定时长的ip并记录日志*/private void cleanIpOverLockTime() {if (jmxLockTime <= 0) {return;}lockMap.entrySet().removeIf(entry -> entry.getValue().isLocked()&& (Duration.between(entry.getValue().getLockDateTime(), LocalDateTime.now()).toMillis() > jmxLockTime)&& logReleasedIp(entry.getKey(), entry.getValue()));}/*** 清除超过校验周期且未锁定的ip*/private void cleanIpOverVerifyUnit() {if (jmxVerifyTimeUint <= 0) {return;}lockMap.entrySet().removeIf(entry -> !entry.getValue().isLocked()&& (System.currentTimeMillis() - entry.getValue().getFirstFailTime() > jmxVerifyTimeUint));}/*** 关闭线程池*/public void close() {this.scheduledExecutorService.shutdownNow();}/*** 获取指定ip是否被锁** @param ip* @return*/public boolean isLocked(String ip) {LockBean lockBean = getJmxLockBeanByIp(ip);return lockBean == null ? false : lockBean.isLocked();}/*** 获取指定ip的锁定时间** @param ip* @return*/public LocalDateTime getLockDateTimeByIp(String ip) {LockBean lockBean = getJmxLockBeanByIp(ip);return lockBean == null ? null : lockBean.getLockDateTime();}/*** 获取指定ip的失败次数** @param ip* @return*/public int getFailedCountByIp(String ip) {LockBean lockBean = getJmxLockBeanByIp(ip);return lockBean == null ? 0 : lockBean.getFailedCount();}private LockBean getLockBeanByIp(String ip) {if (StringUtils.isBlank(ip)) {return null;} else {return lockMap.get(ip);}}/*** 释放指定的被锁ip** @param ip*/public void releaseLockIp(String ip) {if (StringUtils.isBlank(ip)) {return;}LockBean lockBean = lockMap.remove(ip);if (lockBean != null) {logReleasedIp(ip, lockBean);}}/*** 日志记录被锁ip释放** @param ip* @param lockBean* @return*/private boolean logReleasedIp(String ip, LockBean lockBean) {if (LOGGER.isInfoEnabled()) {StringBuilder sb = new StringBuilder("the failed client ");sb.append(ip).append(" has been released,the lock time is ").append(FORMATTER.format(lockBean.getLockDateTime()));LOGGER.info(sb.toString());}return true;}/*** 添加失败的ip到缓存,校验失败,错误次数加1,当达到最大错误次数,锁定** @param ip* @return 锁定返回true 未锁定返回false*/public boolean addFailedIp(String ip) {LockBean lockBean = lockMap.computeIfAbsent(ip, key -> new LockBean());int failedCount = lockBean.incrementAndGetFailedCount();if (failedCount >= jmxMaxFailedCount) {lockBean.lock();StringBuilder sb = new StringBuilder("the failed client ");sb.append(ip).append(" has been locked,the lock time is ").append(FORMATTER.format(lockBean.getLockDateTime()));if (LOGGER.isInfoEnabled()) {LOGGER.info(sb.toString());}return true;}return false;}/*** 抑制日志只打印一次** @param ip* @param logMsg*/public void logLocked(String ip, String logMsg) {lockMap.computeIfPresent(ip, (key, value) -> {if (!value.isLockLogged()) {LOGGER.error(logMsg);value.setLockLogged(true);}return value;});}public boolean isLockOption() {return lockOption;}public void setLockOption(boolean lockOption) {this.lockOption = lockOption;}public int getMaxFailedCount() {return maxFailedCount;}public void setMaxFailedCount(int maxFailedCount) {this.maxFailedCount = maxFailedCount;}public Long getVerifyTimeUint() {return verifyTimeUint;}public void setVerifyTimeUint(Long verifyTimeUint) {this.verifyTimeUint = verifyTimeUint;}public Long getLockTime() {return lockTime;}public void setLockTime(Long lockTime) {this.lockTime = lockTime;}
}

Service服务类,执行真正的业务逻辑,这里未写出,主要参考以下流程图来即可。

Java实现鉴权失败达到一定次数锁定IP并释放到期IP相关推荐

  1. springboot+shiro前后端分离过程中跨域问题、sessionId问题、302鉴权失败问题

    写在前面:2020年2月29号修改该文章,之前针对302鉴权失败问题的解决方案存在 "WebUtils.toHttp 往返回response写返回值的时候出现回写跨域问题".现已进 ...

  2. 关于feign开启hystrix导致用户鉴权失败

    关于feign开启hystrix熔断导致用户鉴权失败的原因是: feign的hystrix熔断默认机制是线程池隔离.而代码在获取用户权限信息时又是线程池处理,所以导致每次获取用户信息为null. 处理 ...

  3. androidstudio 引入百度或者高德地图 鉴权失败

    问题描述:今天尝试在Android项目中引入地图功能,刚开始尝试了百度地图,获取sha1,填写packageName等操作非常流畅,但是测试的时候,发现地图只显示一堆网格,提示鉴权失败,让去论坛自己查 ...

  4. spring-cloud-gateway GlobalFilter 自定义鉴权失败 返回数据结构

    1. 在使用 spring-cloud-gateway 拦截鉴权的时候的时候,错误一般返回401. 我们该如果自定义自己的返回权限呢.图中红色部分就是自定义鉴权失败返回数据结构. @Configura ...

  5. 腾讯云API接口鉴权v3 鉴权失败问题 AuthFailure.SignatureFailure

    最近我开发的CRM项目正好在做营销短信的功能,这个功能需要对接腾讯云的第三方短信接口.众所周知,对接接口最难的就是鉴权部分了,毕竟为了安全嘛.云API鉴权一直是比较晦涩难懂的,建议大家还是去githu ...

  6. android 高德地图SDK报 KEY鉴权失败

    android 高德地图SDK报 KEY鉴权失败. 一般在项目被转移,或项目使用的SDK被其他项目使用时,会发生这种情况! 解决办法 进入高德地图api控制台 点应用管理-我的应用-创建应用 - 根据 ...

  7. 点盾云输入激活码激活视频时,显示鉴权失败无效激活码怎么办?

    如今许多培训机构或者经营网课视频的商家,都会选择使用点盾云视频加密软件来保护自己的视频不被随意传播泄漏传播.那么在使用过程中或多或少会遇到一些不明白的问题,遇到不明白不知道怎样处理的问题应该怎么办呢? ...

  8. java 接口鉴权_安全|Java中使用JWT生成Token进行接口鉴权实现

    先介绍下利用JWT进行鉴权的思路: 1.用户发起登录请求. 2.服务端创建一个加密后的JWT信息,作为Token返回. 3.在后续请求中JWT信息作为请求头,发给服务端. 4.服务端拿到JWT之后进行 ...

  9. 钉钉开放文档——JSAPI鉴权失败

    钉钉鉴权后路由跳转调用定位(需要鉴权)报错 如下 13:32:28.240 [http-nio-8066-exec-6] INFO c.a.w.c.c.CommonController -[uploa ...

  10. ak sk认证java demo_AK-SK鉴权

    插件名称类别 名称 描述 属性 服务插件 AK/SK 鉴权 gw-ak_sk_auth 用户鉴权 功能描述 配置自己的AK/SK,或是使用网关自动生成的AK/SK,完成认证. 客户端涉及的AK/SK签 ...

最新文章

  1. 没有学不会的C++:用户自定义的隐式类型转换
  2. json中怎么去掉[]外的引号_SEO优化中怎么做站内和站外的锚文本
  3. Android 的 SDK Manager 无法启动 闪退解决方法
  4. 第三天2017/03/30(上午:二级指针的(输入)内存模型:(共三种模型))
  5. 剖析boot.img的制作流程
  6. EasyNVR支持的摄像机、NVR设备接入类型以及关于国标设备是否支持接入EasyNVR无插件流媒体服务器...
  7. C语言基础语法总结(一)
  8. IDEA快捷键及使用技巧
  9. 斐波那契数列快速算法详解
  10. Android集成支付宝支付
  11. js中substr与substring的区别
  12. python函数的基本使用_Python学习笔记——主要函数及基本使用(与C的对比)
  13. Activity的Launch mode详解 singleTask正解
  14. Linux之VNC远程桌面安装和使用
  15. 函数声明应该写在什么位置?main函数里面还是前面?(都可以,只要在调用语句的前面即可)
  16. msg1500说明书_MSG1500刷机笔记
  17. ai智能写作如何快速写文?
  18. 汉字转拼音的C++实现及原理_gb2312区位码篇
  19. 庖丁解牛Transformer原理
  20. 哪些产品要做UV老化测试?

热门文章

  1. Word添加脚注自定义标记
  2. HTML常用标签总结 [建议收藏]
  3. 批量提取html文字,批量提取网页内容(全自动)
  4. pythonexcel怎么读_python怎么读excel
  5. pr电子相册制作思路及常用快捷键
  6. Premiere常用快捷键
  7. 中国交通标志检测数据集
  8. html中px em pt区别介绍
  9. doubanactivity_android activity堆栈创建与查找
  10. dw33d最新固件openwrt_【矿渣们的救赎】の 小米路由器mini刷OpenWrt