前言
在登录模块出现暴力碰撞测试的安全漏洞时,我们最常见的方案就是需要记录账密错误,当达到一定错误阈值(比如5次)时,就会锁定该账号一定时间(如10分钟),等到锁定时间过了之后再允许用户继续登录,这样就能有效的抵御恶意暴力破解系统登录账号密码。
而具体实现又有很多种方式,最常见的就是如果系统框架引入了redis分布式缓存,就可以直接将信息缓存到redis中;也可以直接将错误次数,错误时间持久化到数据库中,每次登录校验账密之前都从数据库中查出来做判断逻辑;而今天我们讨论的是最简单的本地缓存方式,利用GuavaCache将本地数据缓存到JVM内存中,实现简单的本地数据缓存。
一、暴力破解漏洞描述
暴力破解-攻击者可利用该漏洞无限次提交用户名密码,从而可以暴力破解系统用户名及密码,如果暴力破解成功,攻击者可以登录到系统进行管理和查看网站敏感信息。
二、Guava cache简介
Guava cache是一个支持高并发的线程安全的本地缓存。多线程情况下也可以安全的访问或者更新Cache。这些都是借鉴了ConcurrentHashMap的结果,不过,guava cache 又有自己的特性 :
“automatic loading of entries into the cache”
即 :当cache中不存在要查找的entry的时候,它会自动执行用户自定义的加载逻辑,加载成功后再将entry存入缓存并返回给用户未过期的entry,如果不存在或者已过期,则需要load,同时为防止多线程并发下重复加载,需要先锁定,获得加载资格的线程(获得锁的线程)创建一个LoadingValueRefrerence并放入map中,其他线程等待结果返回。
三、GuavaCache本地缓存实现
1.测试实现方法
代码如下(示例):
package com.dd.pp.user.auth.server;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @Author:张绪升
* @Date:2024/8/30 11:19
*/
//@SpringBootTest
@Slf4j
public class GuavaCacheTest {
// 使用Guava Cache来存储用户名和解锁时间
private LoadingCache<String, Integer> userLockUntilTimes;
// 初始化一个缓存实例
public void userLockService(int lockDurationMinutes, int maximumSize) {
this.userLockUntilTimes = CacheBuilder.newBuilder()
.expireAfterAccess(lockDurationMinutes, TimeUnit.SECONDS)
.maximumSize(maximumSize)
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
// 初始化时间为0,表示用户未被锁定
return 0;
}
});
}
/**
* 验证用户密码是否正确,如果错误,查询缓存中最新错误次数,并执行+1操作后更新缓存数据
* @param userName 用户名
* @param password 密码
* @param lockDurationMinutes 锁定时间(分钟)
* @return 是否锁定用户
*/
@Synchronized
public boolean validatePassword(String userName, String password