电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

詳解Java分布式IP限流和防止惡意IP攻擊方案

瀏覽:4日期:2022-09-03 15:10:45

前言

限流是分布式系統(tǒng)設(shè)計(jì)中經(jīng)常提到的概念,在某些要求不嚴(yán)格的場(chǎng)景下,使用Guava RateLimiter就可以滿足。但是Guava RateLimiter只能應(yīng)用于單進(jìn)程,多進(jìn)程間協(xié)同控制便無(wú)能為力。本文介紹一種簡(jiǎn)單的處理方式,用于分布式環(huán)境下接口調(diào)用頻次管控。

如何防止惡意IP攻擊某些暴露的接口呢(比如某些場(chǎng)景下短信驗(yàn)證碼服務(wù))?本文介紹一種本地緩存和分布式緩存集成方式判斷遠(yuǎn)程IP是否為惡意調(diào)用接口的IP。

分布式IP限流

思路是使用redis incr命令,完成一段時(shí)間內(nèi)接口請(qǐng)求次數(shù)的統(tǒng)計(jì),以此來(lái)完成限流相關(guān)邏輯。

private static final String LIMIT_LUA = 'local my_limit = redis.call(’incr’, KEYS[1])n' + ' if tonumber(my_limit) == 1 thenn' + ' redis.call(’expire’, KEYS[1], ARGV[1])n' + ' return 1n' + ' elseif tonumber(my_limit) > tonumber(ARGV[2]) thenn' + ' return 0n' + ' elsen' + ' return 1n' + ' endn';

這里為啥時(shí)候用lua腳本來(lái)實(shí)現(xiàn)呢?因?yàn)橐WCincr命令和expire命令的原子性操作。KEYS[1]代表自增key值, ARGV[1]代表過(guò)期時(shí)間,ARGV[2]代表最大頻次,明白了這些參數(shù)的含義,整個(gè)lua腳本邏輯也就不言而喻了。

/** * @param limitKey 限制Key值 * @param maxRate 最大速率 * @param expire Key過(guò)期時(shí)間 */public boolean access(String limitKey, int maxRate, int expire) { if (StringUtils.isBlank(limitKey)) { return true; } String cacheKey = LIMIT_KEY_PREFIX + limitKey; return REDIS_SUCCESS_STATUS.equals( this.cacheService.eval( LIMIT_LUA , Arrays.asList(cacheKey) , Arrays.asList(String.valueOf(expire), String.valueOf(maxRate)) ).toString() );}public void unlimit(String limitKey) { if (StringUtils.isBlank(limitKey)) { return; } String cacheKey = LIMIT_KEY_PREFIX + limitKey; this.cacheService.decr(cacheKey);}

access方法用來(lái)判斷 limitKey 是否超過(guò)了最大訪問(wèn)頻次。緩存服務(wù)對(duì)象(cacheService)的eval方法參數(shù)分別是lua腳本、key list、value list。

unlimit方法其實(shí)就是執(zhí)行redis decr操作,在某些業(yè)務(wù)場(chǎng)景可以回退訪問(wèn)頻次統(tǒng)計(jì)。

防止惡意IP攻擊

由于某些對(duì)外暴露的接口很容易被惡意用戶攻擊,必須做好防范措施。最近我就遇到了這么一種情況,我們一個(gè)快應(yīng)用產(chǎn)品,短信驗(yàn)證碼服務(wù)被惡意調(diào)用了。通過(guò)后臺(tái)的日志發(fā)現(xiàn),IP固定,接口調(diào)用時(shí)間間隔固定,明顯是被人利用了。雖然我們針對(duì)每個(gè)手機(jī)號(hào)每天發(fā)送短信驗(yàn)證碼的次數(shù)限制在5次以內(nèi)。但是短信驗(yàn)證碼服務(wù)每天這樣被重復(fù)調(diào)用,會(huì)打擾用戶并產(chǎn)生投訴。針對(duì)這種現(xiàn)象,簡(jiǎn)單的做了一個(gè)方案,可以自動(dòng)識(shí)別惡意攻擊的IP并加入黑名單。

思路是這樣的,針對(duì)某些業(yè)務(wù)場(chǎng)景,約定在一段時(shí)間內(nèi)同一個(gè)IP訪問(wèn)最大頻次,如果超過(guò)了這個(gè)最大頻次,那么就認(rèn)為是非法IP。識(shí)別了非法IP后,把IP同時(shí)放入本地緩存和分布式緩存中。非法IP再次訪問(wèn)的時(shí)候,攔截器發(fā)現(xiàn)本地緩存(沒(méi)有則去分布式緩存)有記錄這個(gè)IP,直接返回異常狀態(tài),不會(huì)繼續(xù)執(zhí)行正常業(yè)務(wù)邏輯。

Guava本地緩存集成Redis分布式緩存

public abstract class AbstractCombineCache<K, V> { private static Logger LOGGER = LoggerFactory.getLogger(AbstractCombineCache.class); protected Cache<K, V> localCache; protected ICacheService cacheService; public AbstractCombineCache(Cache<K, V> localCache, ICacheService cacheService) { this.localCache = localCache; this.cacheService = cacheService; } public Cache<K, V> getLocalCache() { return localCache; } public ICacheService getCacheService() { return cacheService; } public V get(K key) { //只有LoadingCache對(duì)象才有g(shù)et方法,如果本地緩存不存在key值, 會(huì)執(zhí)行CacheLoader的load方法,從分布式緩存中加載。 if (localCache instanceof LoadingCache) { try {return ((LoadingCache<K, V>) localCache).get(key); } catch (ExecutionException e) {LOGGER.error(String.format('cache key=%s loading error...', key), e);return null; } catch (CacheLoader.InvalidCacheLoadException e) {//分布式緩存中不存在這個(gè)keyLOGGER.error(String.format('cache key=%s loading fail...', key));return null; } } else { return localCache.getIfPresent(key); } } public void put(K key, V value, int expire) { this.localCache.put(key, value); String cacheKey = key instanceof String ? (String) key : key.toString(); if (value instanceof String) { this.cacheService.setex(cacheKey, (String) value, expire); } else { this.cacheService.setexObject(cacheKey, value, expire); } }}

AbstractCombineCache這個(gè)抽象類封裝了guava本地緩存和redis分布式緩存操作,可以降低分布式緩存壓力。

防止惡意IP攻擊緩存服務(wù)

public class IPBlackCache extends AbstractCombineCache<String, Object> { private static Logger LOGGER = LoggerFactory.getLogger(IPBlackCache.class); private static final String IP_BLACK_KEY_PREFIX = 'wmhipblack_'; private static final String REDIS_SUCCESS_STATUS = '1'; private static final String IP_RATE_LUA = 'local ip_rate = redis.call(’incr’, KEYS[1])n' + ' if tonumber(ip_rate) == 1 thenn' + ' redis.call(’expire’, KEYS[1], ARGV[1])n' + ' return 1n' + ' elseif tonumber(ip_rate) > tonumber(ARGV[2]) thenn' + ' return 0n' + ' elsen' + ' return 1n' + ' endn'; public IPBlackCache(Cache<String, Object> localCache, ICacheService cacheService) { super(localCache, cacheService); } /** * @param ipKey IP * @param maxRate 最大速率 * @param expire 過(guò)期時(shí)間 */ public boolean ipAccess(String ipKey, int maxRate, int expire) { if (StringUtils.isBlank(ipKey)) { return true; } String cacheKey = IP_BLACK_KEY_PREFIX + ipKey; return REDIS_SUCCESS_STATUS.equals(this.cacheService.eval( IP_RATE_LUA , Arrays.asList(cacheKey) , Arrays.asList(String.valueOf(expire), String.valueOf(maxRate))).toString() ); } /** * @param ipKey IP */ public void removeIpAccess(String ipKey) { if (StringUtils.isBlank(ipKey)) { return; } String cacheKey = IP_BLACK_KEY_PREFIX + ipKey; try { this.cacheService.del(cacheKey); } catch (Exception e) { LOGGER.error(String.format('%s, ip access remove error...', ipKey), e); } }}

沒(méi)有錯(cuò),IP_RATE_LUA 這個(gè)lua腳本和上面說(shuō)的限流方案對(duì)應(yīng)的lua腳本是一樣的。

IPBlackCache繼承了AbstractCombineCache,構(gòu)造函數(shù)需要guava的本地Cache對(duì)象和redis分布式緩存服務(wù)ICacheService 對(duì)象。

ipAccess方法用來(lái)判斷當(dāng)前ip訪問(wèn)次數(shù)是否在一定時(shí)間內(nèi)已經(jīng)達(dá)到了最大訪問(wèn)頻次。

removeIpAccess方法是直接移除當(dāng)前ip訪問(wèn)頻次統(tǒng)計(jì)的key值。

防止惡意IP攻擊緩存配置類

@Configurationpublic class IPBlackCacheConfig { private static final String IPBLACK_LOCAL_CACHE_NAME = 'ip-black-cache'; private static Logger LOGGER = LoggerFactory.getLogger(IPBlackCacheConfig.class); @Autowired private LimitConstants limitConstants; @Bean public IPBlackCache ipBlackCache(@Autowired ICacheService cacheService) { GuavaCacheBuilder cacheBuilder = new GuavaCacheBuilder<String, Object>(IPBLACK_LOCAL_CACHE_NAME); cacheBuilder.setCacheBuilder(CacheBuilder.newBuilder() .initialCapacity(100) .maximumSize(10000) .concurrencyLevel(10) .expireAfterWrite(limitConstants.getIpBlackExpire(), TimeUnit.SECONDS) .removalListener((RemovalListener<String, Object>) notification -> { String curTime = LocalDateTime.now().toString(); LOGGER.info(notification.getKey() + ' 本地緩存移除時(shí)間:' + curTime); try {cacheService.del(notification.getKey());LOGGER.info(notification.getKey() + ' 分布式緩存移除時(shí)間:' + curTime); } catch (Exception e) {LOGGER.error(notification.getKey() + ' 分布式緩存移除異常...', e); } }) ); cacheBuilder.setCacheLoader(new CacheLoader<String, Object>() { @Override public Object load(String key) {try { Object obj = cacheService.getString(key); LOGGER.info(String.format('從分布式緩存中加載key=%s, value=%s', key, obj)); return obj;} catch (Exception e) { LOGGER.error(key + ' 從分布式緩存加載異常...', e); return null;} } }); Cache<String, Object> localCache = cacheBuilder.build(); IPBlackCache ipBlackCache = new IPBlackCache(localCache, cacheService); return ipBlackCache; }}

注入redis分布式緩存服務(wù)ICacheService對(duì)象。

通過(guò)GuavaCacheBuilder構(gòu)建guava本地Cache對(duì)象,指定初始容量(initialCapacity)、最大容量(maximumSize)、并發(fā)級(jí)別、key過(guò)期時(shí)間、key移除監(jiān)聽(tīng)器。最終要的是CacheLoader這個(gè)參數(shù),是干什么用的呢?如果GuavaCacheBuilder指定了CacheLoader對(duì)象,那么最終創(chuàng)建的guava本地Cache對(duì)象是LoadingCache類型(參考AbstractCombineCache類的get方法),LoadingCache對(duì)象的get方法首先從內(nèi)存中獲取key對(duì)應(yīng)的value,如果內(nèi)存中不存在這個(gè)key則調(diào)用CacheLoader對(duì)象的load方法加載key對(duì)應(yīng)的value值,加載成功后放入內(nèi)存中。

最后通過(guò)ICacheService對(duì)象和guava本地Cache對(duì)象創(chuàng)建IPBlackCache(防止惡意IP攻擊緩存服務(wù))對(duì)象。

攔截器里惡意IP校驗(yàn)

定義一個(gè)注解,標(biāo)注在指定方法上,攔截器里會(huì)識(shí)別這個(gè)注解。

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface IPBlackLimit { //統(tǒng)計(jì)時(shí)間內(nèi)最大速率 int maxRate(); //頻次統(tǒng)計(jì)時(shí)間 int duration(); //方法名稱 String method() default StringUtils.EMPTY;}

攔截器里加入ipAccess方法,校驗(yàn)遠(yuǎn)程IP是否為惡意攻擊的IP。

/*** @param method 需要校驗(yàn)的方法* @param remoteAddr 遠(yuǎn)程IP*/private boolean ipAccess(Method method, String remoteAddr) { if (StringUtils.isBlank(remoteAddr) || !AnnotatedElementUtils.isAnnotated(method, IPBlackLimit.class)) { return true; } IPBlackLimit ipBlackLimit = AnnotatedElementUtils.getMergedAnnotation(method, IPBlackLimit.class); try { String ip = remoteAddr.split(',')[0].trim(); String cacheKey = 'cipb_' + (StringUtils.isBlank(ipBlackLimit.method()) ? ip : String.format('%s_%s', ip, ipBlackLimit.method())); String beginAccessTime = (String) ipBlackCache.get(cacheKey); if (StringUtils.isNotBlank(beginAccessTime)) { LocalDateTime beginTime = LocalDateTime.parse(beginAccessTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME), endTime = LocalDateTime.now(); Duration duration = Duration.between(beginTime, endTime); if (duration.getSeconds() >= limitConstants.getIpBlackExpire()) {ipBlackCache.getLocalCache().invalidate(cacheKey);return true; } else {return false; } } boolean access = ipBlackCache.ipAccess(cacheKey, ipBlackLimit.maxRate(), ipBlackLimit.duration()); if (!access) { ipBlackCache.removeIpAccess(cacheKey); String curTime = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); ipBlackCache.put(cacheKey, curTime, limitConstants.getIpBlackExpire()); } return access; } catch (Exception e) { LOGGER.error(String.format('method=%sï¼remoteAddr=%s, ip access check error.', method.getName(), remoteAddr), e); return true; }}

remoteAddr取的是X-Forwarded-For對(duì)應(yīng)的值。利用 remoteAddr 構(gòu)造 cacheKey 參數(shù),通過(guò)IPBlackCache判斷 cacheKey 是否存在。

如果是 cacheKey 存在的請(qǐng)求,判斷黑名單IP限制是否已經(jīng)到達(dá)有效期,如果已經(jīng)超過(guò)有效期則清除本地緩存和分布式緩存的 cacheKey ,請(qǐng)求合法;如果沒(méi)有超過(guò)有效期則請(qǐng)求非法。

否則是 cacheKey 不存在的請(qǐng)求,使用IPBlackCache對(duì)象的ipAccess方法統(tǒng)計(jì)一定時(shí)間內(nèi)的訪問(wèn)頻次,如果頻次超過(guò)最大限制,表明是非法請(qǐng)求IP,需要往IPBlackCache對(duì)象寫(xiě)入“ cacheKey =當(dāng)前時(shí)間”。

總結(jié)

本文的兩種方案都使用redis incr命令,如果不是特殊業(yè)務(wù)場(chǎng)景,redis的key要指定過(guò)期時(shí)間,嚴(yán)格來(lái)講需要保證incr和expire兩個(gè)命令的原子性,所以使用lua腳本方式。如果沒(méi)有那么嚴(yán)格,完全可以先setex(設(shè)置key,value,過(guò)期時(shí)間),然后再incr(注: incr不會(huì)更新key的有效期 )。本文的設(shè)計(jì)方案僅供參考,并不能應(yīng)用于所有的業(yè)務(wù)場(chǎng)景。

到此這篇關(guān)于詳解Java分布式IP限流和防止惡意IP攻擊方案的文章就介紹到這了,更多相關(guān)Java 分布式IP限流和防止惡意IP內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 酒店品牌设计-酒店vi设计-酒店标识设计【国际级】VI策划公司 | 净化车间装修_合肥厂房无尘室设计_合肥工厂洁净工程装修公司-安徽盛世和居装饰 | 挤奶设备过滤纸,牛奶过滤纸,挤奶机过滤袋-济南蓝贝尔工贸有限公司 | 北京开业庆典策划-年会活动策划公司-舞龙舞狮团大鼓表演-北京盛乾龙狮鼓乐礼仪庆典策划公司 | 冷水机-冰水机-冷冻机-冷风机-本森智能装备(深圳)有限公司 | 飞行者联盟-飞机模拟机_无人机_低空经济_航空技术交流平台 | 贴片电容代理-三星电容-村田电容-风华电容-国巨电容-深圳市昂洋科技有限公司 | 福尔卡(北京)新型材料技术股份有限公司| 小型气象站_车载气象站_便携气象站-山东风途物联网 | 钢格板|镀锌钢格板|热镀锌钢格板|格栅板|钢格板|钢格栅板|热浸锌钢格板|平台钢格板|镀锌钢格栅板|热镀锌钢格栅板|平台钢格栅板|不锈钢钢格栅板 - 专业钢格板厂家 | 政府园区专业委托招商平台_助力企业选址项目快速落地_东方龙商务集团 | 液氮罐_液氮容器_自增压液氮罐_杜瓦瓶_班德液氮罐厂家 | 知名电动蝶阀,电动球阀,气动蝶阀,气动球阀生产厂家|价格透明-【固菲阀门官网】 | 网架支座@球铰支座@钢结构支座@成品支座厂家@万向滑动支座_桥兴工程橡胶有限公司 | 生产加气砖设备厂家很多,杜甫机械加气砖设备价格公道 | 篷房[仓储-婚庆-展览-活动]生产厂家-江苏正德装配式帐篷有限公司 | 钢衬四氟管道_钢衬四氟直管_聚四氟乙烯衬里管件_聚四氟乙烯衬里管道-沧州汇霖管道科技有限公司 | 临时厕所租赁_玻璃钢厕所租赁_蹲式|坐式厕所出租-北京慧海通 | 厦门网站建设_厦门网站设计_小程序开发_网站制作公司【麦格科技】 | 魔方网-培训咨询服务平台| 棕刚玉-白刚玉厂家价格_巩义市东翔净水材料厂 | 致胜管家软件服务【在线免费体验】 | 飞象网 - 通信人每天必上的网站 全球化工设备网—化工设备,化工机械,制药设备,环保设备的专业网络市场。 | 睿婕轻钢别墅_钢结构别墅_厂家设计施工报价 | 小程序开发公司-小程序制作-微信小程序开发-小程序定制-咏熠软件 | 全自动定氮仪-半自动凯氏定氮仪厂家-祎鸿仪器 | loft装修,上海嘉定酒店式公寓装修公司—曼城装饰 | HYDAC过滤器,HYDAC滤芯,现货ATOS油泵,ATOS比例阀-东莞市广联自动化科技有限公司 | 彭世修脚_修脚加盟_彭世修脚加盟_彭世足疗加盟_足疗加盟连锁_彭世修脚技术培训_彭世足疗 | 衬氟止回阀_衬氟闸阀_衬氟三通球阀_衬四氟阀门_衬氟阀门厂-浙江利尔多阀门有限公司 | 抓斗式清污机|螺杆式|卷扬式启闭机|底轴驱动钢坝|污水处理闸门-方源水利机械 | 米顿罗计量泵(科普)——韬铭机械 | 北京易通慧公司从事北京网站优化,北京网络推广、网站建设一站式服务商-北京网站优化公司 | 英思科GTD-3000EX(美国英思科气体检测仪MX4MX6)百科-北京嘉华众信科技有限公司 | 全自动变压器变比组别测试仪-手持式直流电阻测试仪-上海来扬电气 | 防水套管厂家_刚性防水套管_柔性防水套管_不锈钢防水套管-郑州中泰管道 | 碳化硅,氮化硅,冰晶石,绢云母,氟化铝,白刚玉,棕刚玉,石墨,铝粉,铁粉,金属硅粉,金属铝粉,氧化铝粉,硅微粉,蓝晶石,红柱石,莫来石,粉煤灰,三聚磷酸钠,六偏磷酸钠,硫酸镁-皓泉新材料 | 超声波焊接机,振动摩擦焊接机,激光塑料焊接机,超声波焊接模具工装-德召尼克(常州)焊接科技有限公司 | 污泥烘干机-低温干化机-工业污泥烘干设备厂家-焦作市真节能环保设备科技有限公司 | 不干胶标签-不干胶贴纸-不干胶标签定制-不干胶标签印刷厂-弗雷曼纸业(苏州)有限公司 | 多功能干燥机,过滤洗涤干燥三合一设备-无锡市张华医药设备有限公司 |