微服务系统被恶意攻击做的请求限流处理
1.起因
项目是微服务开发的,在正式项目的上线中遭遇了其他服务的恶意攻击。参考了网上的资料。大部分都是在gateway做了请求限流。我的方法也是一样的。
参考链接:https://www.jianshu.com/p/2ba07a16efa8?utm_campaign=hugo
2.准备阶段
1.拥有gateway的微服务
2.拥有redis服务
3.执行流程
3.1 pom引入
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.guo.springcloud</groupId> <artifactId>sc-gateway-rate-limiter</artifactId> <version>1.0.0.RELEASE</version> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <spring.boot.version>2.2.4.RELEASE</spring.boot.version> <spring.cloud.version>Hoxton.SR1</spring.cloud.version> <spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version> </properties> <!-- 引入 Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者 BOM 文件,进行依赖版本的管理,防止不兼容。 在 https://dwz.cn/mcLIfNKt 文章中,Spring Cloud Alibaba 开发团队推荐了三者的依赖关系 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 引入 Spring Cloud Gateway 相关依赖,使用它作为网关,并实现对其的自动配置 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- 实现对 Spring Data Redis 的自动化配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> </project>
记得不要引入springboot的依赖。会发生冲突
配置yml文件
server: port: 8094 spring: application: name: sc-gateway-application cloud: ## Spring Cloud Gateway 配置项,对应 GatewayProperties 类 gateway: # 路由配置项,对应 RouteDefinition 数组 routes: - id: jiansu # 路由的编号 uri: https://www.jianshu.com/u/ea0462d5074c # 路由到的目标地址(上面作者的简书主页地址) predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - Path=/jianshu/* #请求的路径 filters: - StripPrefix=1 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1 # 令牌桶的每秒放的数量 redis-rate-limiter.burstCapacity: 2 # 令牌桶的最大令牌数 key-resolver: "#{@ipKeyResolver}" # 获取限流 KEY 的 Bean 的名字 - id: guo # 路由的编号 uri: https://www.126.com # 路由的目标地址 predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - Path=/* filters: # 过滤器,对请求进行拦截,实现自定义的功能,对应 FilterDefinition 数组 - StripPrefix=1 ##### Redis 配置项 ##### redis: host: 192.168.0.31 port: 6379
编写获取ip的工具类
WebFluxUtil
package com.erbadagang.springcloud.gateway.config; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Optional; public class WebFluxUtil { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WebFluxUtil.class); private static final String IP_UNKNOWN = "unknown"; private static final String IP_LOCAL = "127.0.0.1"; private static final String IPV6_LOCAL = "0:0:0:0:0:0:0:1"; private static final int IP_LEN = 15; /** * 获取用户真实IP地址,不直接使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址, * * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢? * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。 * * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, * 192.168.1.100 * * 用户真实IP为: 192.168.1.110 * * @param request * @return */ public static String getIpAddress(ServerHttpRequest request) { HttpHeaders headers = request.getHeaders(); String ipAddress = headers.getFirst("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) { ipAddress = headers.getFirst("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) { ipAddress = headers.getFirst("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) { ipAddress = Optional.ofNullable(request.getRemoteAddress()) .map(address -> address.getAddress().getHostAddress()) .orElse(""); if (IP_LOCAL.equals(ipAddress)|| IPV6_LOCAL.equals(ipAddress)) { // 根据网卡取本机配置的IP try { InetAddress inet = InetAddress.getLocalHost(); ipAddress = inet.getHostAddress(); } catch (UnknownHostException e) { log.error(e.getMessage()); } } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > IP_LEN) { int index = ipAddress.indexOf(","); if (index > 0) { ipAddress = ipAddress.substring(0, index); } } return ipAddress; } }
编写限流配置
GatewayConfig
package com.erbadagang.springcloud.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @description 限流 KEY 的 Bean ,通过解析请求的来源 IP 作为限流 KEY,这样我们就能实现基于 IP 的请求限流。
* @ClassName: GatewayConfig
* @author: 郭秀志 jbcode@126.com
* @date: 2020/7/29 15:26
* @Copyright:
*/
@Configuration
public class GatewayConfig {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GatewayConfig.class);
@Bean
public KeyResolver ipKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
String hostName = exchange.getRequest().getRemoteAddress().getHostName();
String ipAddress = WebFluxUtil.getIpAddress(exchange.getRequest());
log.info("hostName:{},ipAddress:{}",hostName,ipAddress);
// 获取请求的 IP
return Mono.just(ipAddress);
}
};
}
}
定义filter过滤器
package com.inno.leyin.config; import com.alibaba.fastjson.JSONObject; import com.inno.leyin.common.constant.CommonConstant; import com.inno.leyin.common.enums.ResponseMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ServerWebExchange; import reactor.core.Disposable; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicBoolean; @Configuration public class FilterConfig { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FilterConfig.class); @Bean @Order(-1) public GlobalFilter ipFilter() { return new IpFilter(); } @Autowired RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory; @Autowired RateLimiter rateLimiter; @Autowired StringRedisTemplate stringRedisTemplate; public class IpFilter implements GlobalFilter, Ordered { //只要是内网192,172的都过,其他的查询出来是外网。判断是否在白名单,不在打死。在黑名单中结束 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("IpFilter前置逻辑"); ServerHttpRequest request = exchange.getRequest(); String ipAddress = WebFluxUtil.getIpAddress(request); Boolean hasKey = stringRedisTemplate.opsForHash().hasKey("IP_BLACK_LIST", ipAddress); if(hasKey){ ServerHttpResponse response = exchange.getResponse(); DataBuffer buffer = sendFailInfo(response); return response.writeWith(Mono.just(buffer)); } Mono<RateLimiter.Response> erbadagang_rate_limiter = rateLimiter.isAllowed("jiansu", ipAddress); AtomicBoolean flag= new AtomicBoolean(false); Disposable subscribe = erbadagang_rate_limiter.doOnSuccess(d -> { boolean allowed = d.isAllowed(); System.out.println(allowed); log.info("allowed:{},ipAddress:{}", allowed,ipAddress); if(!allowed){//代表超出阈值 //redisService.setHash("ip",ipAddress,1); log.info("allowed:{},ipAddress:{}", allowed,ipAddress); stringRedisTemplate.opsForHash().put("IP_BLACK_LIST",ipAddress,"1"); log.info("ipAddress:{} success"); flag.set(true); } }).subscribe(); log.info("flag:{} ",flag); if(flag.get()){ ServerHttpResponse response = exchange.getResponse(); DataBuffer buffer = sendFailInfo(response); return response.writeWith(Mono.just(buffer)); } return chain.filter(exchange); } // 值越小,优先级越高 // int HIGHEST_PRECEDENCE = -2147483648; // int LOWEST_PRECEDENCE = 2147483647; @Override public int getOrder() { return HIGHEST_PRECEDENCE + 100; } } private DataBuffer sendFailInfo(ServerHttpResponse response) { JSONObject message = new JSONObject(); message.put("status", ResponseMessage.SENTINEL_ERROR.getCode()); message.put("data", ResponseMessage.SENTINEL_ERROR.getValue()); byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8); DataBuffer buffer = response.bufferFactory().wrap(bits); response.setStatusCode(HttpStatus.UNAUTHORIZED); //指定编码,否则在浏览器中会中文乱码 response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8"); return buffer; } }
测试:
- 启动Redis
- 执行 GatewayApplication, 启动网关代码。
- 使用浏览器,连续快速访问 http://127.0.0.1:8094/jianshu 地址,将会出现被限流为空白页。
这是一个demo,实际使用的时候需要依赖其他的jar包,所以在使用的时候出现自动配置启动的时候,把该配置移除
建议在生产环境中,
异常问题以及解决方案
在我的系统中开发环境和测试环境都是单机版本,所以不会出现问题,但是在生产环境下出现了问题。
-- NOSCRIPT No matching script. Please use EVAL.
-- ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression
这个原因是阿里云集群环境做了限制。解决方案有两个。
1.https://blog.csdn.net/LHQChocolate/article/details/108668052
2.https://www.jianshu.com/p/6bd82d96ffcf
建议使用第一个
本文来自博客园,作者:小陈子博客,转载请注明原文链接:https://www.cnblogs.com/cj8357475/p/15874700.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)