Appearance
自定义网关过滤器
请求进入网关会碰到三类过滤器:DefaultFilter、当前路由的过滤器、GlobalFilter。请求路由后,会将三者合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
过滤器排序规则:
(1)每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序越靠前。
(2)GlobalFilter 通过实现 Ordered 接口,或者使用 @Order 注解来指定 order 值,由我们自己指定。
(3)路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从1递增。
(4)如果三种过滤器的 order 值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。
1. 自定义全局过滤器
实现 GlobalFilter
和 Ordered
接口,具体需要实现的逻辑代码在filter方法中完成,并给过滤器设置执行顺序order值,可参考如下代码:
java
/**
* 全局黑名单过滤器
*
* @author guoyd
* @create 2019/7/31
* @since 1.0.0
*/
@Component
@Slf4j
public class BlackListGlobalFilter implements GlobalFilter, Ordered {
@Autowired
IPListService ipListService;
/**
* 功能描述:
* 〈黑名单全局过滤器〉
*
* @param exchange
* @param chain
* @return:reactor.core.publisher.Mono<java.lang.Void>
* @since: 1.0.0
* @Author:guoyd
* @Date: 2019/7/31 17:05
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//黑名单启用开关
if (ipListService.getListStatusCache(Constant.BLACK_LIST).equals(Constant.IP_ENABLE)) {
Stream<BlackWhiteIpInfo> stream = ipListService.getIPModelCache();
stream.forEach(
blackWhiteIpInfo -> {
IPModel ipModel = JSON.parseObject(blackWhiteIpInfo.getValue(), IPModel.class);
//nginx反向代理后,所有的remoteAddr实际上变成了nginx的地址,所以改为nginx自动填写ip到请求头中,此处重新获取ip
String address = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(Constant.REMOTE_ADDR_KEY))
.orElse(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
//将","分隔形式的ip段,进行拆分处理
List<IPModel> blackIpList = new LinkedList<>();
if (ipModel.getType().equals(Constant.BLACK_LIST)) {
List<String> ipSegments = Arrays.asList(ipModel.getIp().split(","));
ipSegments.forEach(ip -> {
IPModel ipModel1 = new IPModel();
ipModel1.setIp(ip);
ipModel1.setType(Constant.BLACK_LIST);
blackIpList.add(ipModel1);
});
}
try {
blackIpList.sort(BlackListGlobalFilter::ipCompare);
} catch (Exception e) {
log.error("异常:{}", e);
}
for (IPModel ipMod : blackIpList) {
if (IPUtils.ipMatch(ipMod.getIp(), address)) {
throw new CommonException().setCode(Constant.BLACK_IP_IN).setHttpStatus(HttpStatus.FORBIDDEN);
}
}
}
);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
//全局body体解析(order为MIN_VALUE)必须放在最前,所以这里+1
return Constant.FILTER_ORDER_INTEGER_MIN;
}
}
2. 自定义路由过滤器
此过滤器在使用时需要具体添加到某一个路由上才可以生效,继承抽象类 AbstractGatewayFilterFactory<C>
实现,此过滤器对应的实现类的类名必须是 XXXGatewayFilterFactory
,以 GatewayFilterFactory
结尾。自定义实现类中需要创建一个内部类 Config
用来接收页面所设置的具体参数值,路由过滤器中的order要想生效,不能直接实现Order接口,需要在类中添加一个实现 GatewayFilter
的内部类,然后此内部类去实现 Ordered
接口才可实现(具体实现的代码逻辑可参考如下代码)。自定义过滤器实现完成之后,需要使用@Bean注解完成一个磁过滤器的Bean实现。上述代码实现完成之后,在参数表中添加具体过滤器的配置数据,路由过滤器参数对应的是数据库中的 FILTER_PARAM_INFO
表,表中 FILTER_VISIBLE
控制过滤器是否在过滤器列表中显示, FILTER_CONFIG
字段中的JSON串是过滤器需要配置的参数,前端解析之后展示,此字段如何添加可参考已有的参数数据进行修改实现。过滤器的英文名称为实现类 XXXGatewayFilterFactory
中 GatewayFilterFactory
的名称XXX,表中数据添加完成之后即可使用。
java
//过滤器实现参考
/**
* @Created on 2021/7/21 17:24
* @Description
* @Author: zhaozsa
*/
@Slf4j
public class InstanceLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<InstanceLimiterGatewayFilterFactory.Config> {
/**
* Key-Resolver key.
*/
// public static final String KEY_RESOLVER_KEY = "keyResolver";
//
// private static final String EMPTY_KEY = "____EMPTY_KEY__";
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
/**
* Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
*/
private boolean denyEmptyKey = true;
/** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
public InstanceLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter,
KeyResolver defaultKeyResolver) {
super(InstanceLimiterGatewayFilterFactory.Config.class);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
public KeyResolver getDefaultKeyResolver() {
return defaultKeyResolver;
}
public RateLimiter getDefaultRateLimiter() {
return defaultRateLimiter;
}
public boolean isDenyEmptyKey() {
return denyEmptyKey;
}
public void setDenyEmptyKey(boolean denyEmptyKey) {
this.denyEmptyKey = denyEmptyKey;
}
public String getEmptyKeyStatusCode() {
return emptyKeyStatusCode;
}
public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
this.emptyKeyStatusCode = emptyKeyStatusCode;
}
@SuppressWarnings("unchecked")
@Override
public GatewayFilter apply(InstanceLimiterGatewayFilterFactory.Config config) {
return new InstanceFilter(config, defaultRateLimiter, defaultKeyResolver);
}
private <T> T getOrDefault(T configValue, T defaultValue) {
return (configValue != null) ? configValue : defaultValue;
}
public static class Config implements HasRouteId {
private KeyResolver keyResolver;
private RateLimiter rateLimiter;
private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;
private Boolean denyEmptyKey;
private String emptyKeyStatus;
private String routeId;
public KeyResolver getKeyResolver() {
return keyResolver;
}
public InstanceLimiterGatewayFilterFactory.Config setKeyResolver(KeyResolver keyResolver) {
this.keyResolver = keyResolver;
return this;
}
public RateLimiter getRateLimiter() {
return rateLimiter;
}
public InstanceLimiterGatewayFilterFactory.Config setRateLimiter(RateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
return this;
}
public HttpStatus getStatusCode() {
return statusCode;
}
public InstanceLimiterGatewayFilterFactory.Config setStatusCode(HttpStatus statusCode) {
this.statusCode = statusCode;
return this;
}
public Boolean getDenyEmptyKey() {
return denyEmptyKey;
}
public InstanceLimiterGatewayFilterFactory.Config setDenyEmptyKey(Boolean denyEmptyKey) {
this.denyEmptyKey = denyEmptyKey;
return this;
}
public String getEmptyKeyStatus() {
return emptyKeyStatus;
}
public InstanceLimiterGatewayFilterFactory.Config setEmptyKeyStatus(String emptyKeyStatus) {
this.emptyKeyStatus = emptyKeyStatus;
return this;
}
@Override
public void setRouteId(String routeId) {
this.routeId = routeId;
}
@Override
public String getRouteId() {
return this.routeId;
}
}
public class InstanceFilter implements GatewayFilter, Ordered {
private Config config;
/**
* Key-Resolver key.
*/
public static final String KEY_RESOLVER_KEY = "keyResolver";
private static final String EMPTY_KEY = "____EMPTY_KEY__";
private RateLimiter defaultRateLimiter;
private KeyResolver defaultKeyResolver;
/**
* Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
*/
private boolean denyEmptyKey = true;
/** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
public KeyResolver getDefaultKeyResolver() {
return defaultKeyResolver;
}
public RateLimiter getDefaultRateLimiter() {
return defaultRateLimiter;
}
public boolean isDenyEmptyKey() {
return denyEmptyKey;
}
public void setDenyEmptyKey(boolean denyEmptyKey) {
this.denyEmptyKey = denyEmptyKey;
}
public String getEmptyKeyStatusCode() {
return emptyKeyStatusCode;
}
public void setEmptyKeyStatusCode(String emptyKeyStatusCode) {
this.emptyKeyStatusCode = emptyKeyStatusCode;
}
public InstanceFilter(Config config, RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
this.config = config;
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver);
RateLimiter<Object> limiter = getOrDefault(config.rateLimiter,
defaultRateLimiter);
boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey);
HttpStatusHolder emptyKeyStatus = HttpStatusHolder
.parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode));
return resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY)
.flatMap(key -> {
if (EMPTY_KEY.equals(key)) {
if (denyEmpty) {
setResponseStatus(exchange, emptyKeyStatus);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
String routeId = config.getRouteId();
if (routeId == null) {
Route route = exchange
.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
routeId = route.getId();
}
return limiter.isAllowed(routeId, key).flatMap(response -> {
for (Map.Entry<String, String> header : response.getHeaders()
.entrySet()) {
exchange.getResponse().getHeaders().add(header.getKey(),
header.getValue());
}
if (response.isAllowed()) {
return chain.filter(exchange);
}
setResponseStatus(exchange, config.getStatusCode());
// ServerHttpResponse originalResponse = exchange.getResponse();
// DataBufferFactory bufferFactory = originalResponse.bufferFactory();
// LimiterResponse limiterResponse = new LimiterResponse();
// limiterResponse.setCode(HttpStatus.TOO_MANY_REQUESTS.value());
// limiterResponse.setMsg("TOUCH OFF INSTANCE LIMITER");
log.debug("TOUCH OFF INSTANCE LIMITER");
// String lastStr = JSON.toJSONString(limiterResponse);
// byte[] uppedContent = lastStr.getBytes();
// DataBuffer buffer = bufferFactory.wrap(uppedContent);
// return exchange.getResponse().writeWith(Mono.just(buffer));
String[] strings = key.split("@");
throw new CommonException().setCode("429").setMsg("TOUCH OFF INSTANCE LIMITER").setHttpStatus(HttpStatus.TOO_MANY_REQUESTS);
// throw new TooManyResultsException("TOUCH OFF INSTANCE LIMITER "+ strings[1]);
});
});
}
@Override
public int getOrder() {
return Constant.FILTER_ORDER_INSTANCE_LIMITER;
}
}
public static boolean isJSON2(String str) {
boolean result = false;
try {
Object obj = JSON.parse(str);
result = true;
} catch (Exception e) {
result = false;
}
return result;
}
/**
* @description:
* 返回给前端的统一格式
* @param
* @return:
*/
@Data
private class LimiterResponse implements Serializable {
private static final long serialVersionUID = 1L;
private int code;
private boolean success = false;
private String msg = "";
private Object data;
}
}
java
/**
** bean初始化
**/
@Configuration
@RibbonClients(defaultConfiguration = RibbonDefaultConfiguration.class)
@ComponentScan("com.dcits.jupiter.gateway")
@MapperScan("com.dcits.jupiter.gateway.repository.mybatis")
@AutoConfigureAfter(JupiterRibbonAutoConfiguration.class)
@SuppressWarnings("all")
public class JupiterConfiguration {
@Bean
public InstanceLimiterGatewayFilterFactory instanceLimiterGatewayFilterFactory(InstanceRedisRateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) {
return new InstanceLimiterGatewayFilterFactory(defaultRateLimiter, defaultKeyResolver);
}
}
过滤器参数表添加的数据参考:
sql
INSERT INTO "ENS_GATEWAY"."FILTER_PARAM_INFO"("FILTER_ID", "FILTER_NAME", "FILTER_CONFIG", "FILTER_COMMENT",
"FILTER_VISIBLE")
VALUES ('28', 'InstanceLimiter',
'[{"name":"custom-redis-rate-limiter.burstCapacity","desc":"令牌桶容量","placeholder":"10","comment":"令牌桶容量","type":"text","eg":"10"},{"name":"custom-redis-rate-limiter.replenishRate","desc":"令牌桶每秒恢复量","placeholder":"10","comment":"令牌桶恢复速率","type":"text","eg":"10"},{"name":"custom-redis-rate-limiter.key","desc":"key值","placeholder":"10","comment":"key值","type":"text","eg":"10"},{"name":"custom-redis-rate-limiter.value","desc":"value值","placeholder":"10","comment":"value值","type":"text","eg":"10"},{"name":"key-resolver","desc":"resolver的bean名称","placeholder":"#{@requestHeaderFixationKeyResolver}","comment":"resolver的bean名称","type":"text","eg":"resolver的bean名称"},{"name":"rate-limiter","desc":"限流器名称","placeholder":"#{@customRedisRateLimiter}","comment":"限流器名称","type":"text","eg":"限流器名称"}]',
'实例限流过滤器', '0');