springboot项目应用ehcahe-shiro限制短信登录失败次数
需求背景
用户使用手机号和短信验证码登录,项目要求限制用户登录失败的次数,即失败超过6次,就锁定用户账号。
实现思路
以登陆者手机号码 + “errorTimes” 为 key 值进行记录。验证码输入错误后记录次数,验证码输入错误次数达到 6 次以后,将该用户状态设置为锁定。
技术选择
1、MySql 新增缓存表进行记录;
2、使用 ehcache 进行缓存;
3、使用 redis 进行缓存;
进过比较,ehcache 比较契合我们项目的现状,故此选择。
(在网上找到了相关限制登录次数的方法,单都是账号密码登录。取得是数据库中密码而非短信验证码,所以只能自己写了)
实现代码
1、在 main/resources 下新建 config 文件夹,在 config 中新建 ehcache-shiro.xml 文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">
<diskStore path="java.io.tmpdir"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
TTI用于设置对象在cache中的最大闲置时间,就是 在一直不访问这个对象的前提下,这个对象可以在cache中的存活时间。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
TTL用于设置对象在cache中的最大存活时间,就是 无论对象访问或是不访问(闲置),这个对象在cache中的存活时间。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:
Ehcache的三种清空策略;
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!-- 登录错误记录缓存锁定24小时 -->
<cache name="errorLoginCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="86400"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
。java
2、在校验验证码的方法 smsServiceImpl.java 中,写入内容如下:
(验证码对比方法:前端传过来的验证码和发送短信后存储在 session 中的验证码对比)
@Override
public boolean verifySms(String mobile, String code, HttpSession httpSession) {
// 获取 session 验证码
String sessionString = (String) httpSession.getAttribute("sms_login_" + mobile);
//1,创建缓存管理器
CacheManager cm = CacheManager.create(this.getClass().getResourceAsStream("/config/ehcache-shiro.xml"));
//2,获取指定的缓存
Cache cache = cm.getCache("errorLoginCache");
Element element = cache.get(mobile+"errorTimes");
if (sessionString != null) {
// 字符串前四位是验证码,后面是创建时间
String sessionCode = sessionString.substring(0, 4);
// 比较验证码是否一致 return sessionCode.equals(code);
Integer count = 0;
if (!sessionCode.equals(code)){
//4,判断数据
if (element == null) {
//如果用户没有登陆过,登陆次数加1 并放入缓存
cache.put(new Element(mobile+"errorTimes", 1));
} else {
count = (Integer) element.getObjectValue();
if (count > 4){ // 从0到4, 点击6次锁定,所以需要大于4
SysEmployee sysEmployee = sysEmployeeService.findByMobile(mobile);
if (sysEmployee != null && sysEmployee.getStatus() != -1 ){
//修改数据库的状态字段为锁定
sysEmployee.setStatus(-1);
sysEmployeeService.update(sysEmployee);
}
}else{
cache.put(new Element(mobile+"errorTimes", count+1));
}
}
}else{
// 登录成功,删除缓存中的登录错误次数
cache.remove(mobile+"errorTimes");
return true;
}
}
return false;
}
3、在登录方法中,调用上述校验结果判断即可:
if (smsService.verifySms(mobile, code, httpSession)){
try {
Subject subject = getSubject();
if (subject != null)
subject.logout();
subject.login(token);
// 更新登陆记录
sysEmployeeService.saveLoginInfo(getCurrentUser());
return "redirect:/";
} catch (UnknownAccountException | IncorrectCredentialsException | LockedAccountException e) {
attributes.addFlashAttribute("type", "error");
attributes.addFlashAttribute("msg", e.getMessage());
attributes.addFlashAttribute("mobile", mobile);
return "redirect:/login";
} catch (AuthenticationException e) {
attributes.addFlashAttribute("type", "error");
attributes.addFlashAttribute("msg", "认证失败");
attributes.addFlashAttribute("mobile", mobile);
return "redirect:/login";
}
}else{
if (sysEmployee.getStatus() != -1){
attributes.addFlashAttribute("type", "error");
attributes.addFlashAttribute("msg", "验证码错误");
attributes.addFlashAttribute("mobile", mobile);
}else{
attributes.addFlashAttribute("type", "error");
attributes.addFlashAttribute("msg", "账号已被锁定,请联系管理员");
attributes.addFlashAttribute("mobile", mobile);
}
return "redirect:/login";
}
第一下次使用 ehcache,难免有错误疏漏 ,如有不足还请指正。