目录
一、简介
Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:
-
GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP访问限制等等。
public interface GatewayFilter extends ShortcutConfigurable {
String NAME_KEY = "name";
String VALUE_KEY = "value";
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
-
GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
Spring Cloud Gateway自带的GlobalFilter实现类有很多,如下图:
上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。
二、自定义全局过滤器
下面我们通过两个案例说明如何自定义全局过滤器。
(一)、自定义全局过滤器 - 限流过滤器(Token令牌桶算法)
【a】pom.xml中加入如下依赖
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.9</version>
</dependency>
【b】payment8001增加如下方法
/**
* 测试自定义全局过滤器
*/
@GetMapping("/customGatewayGlobalFilter/{name}")
public String customGatewayGlobalFilter(@PathVariable("name") String name) {
return "hello, [customGatewayGlobalFilter] the name is :" + name + ", the server port is " + serverPort;
}
【c】application.yml配置文件
server:
port: 9528
spring:
application:
name: springcloud-gateway
cloud:
gateway:
routes:
########################################【AddRequestHeader GatewayFilter添加请求头】######################################################
- id: payment_service8001_gatewayAddRequestHeader #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/gatewayAddRequestHeader/**
filters:
#将X-Request-red:blue头添加到所有匹配请求的下游请求头中
- AddRequestHeader=X-Request-red, blue
########################################【AddRequestParameter GatewayFilter添加请求参数】######################################################
- id: payment_service8001_gatewayAddRequestParameter #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/gatewayAddRequestParameter
filters: #过滤器(filters:过滤器,过滤规则)
# 添加指定参数
- AddRequestParameter=name, weishihuai
########################################【AddResponseHeader GatewayFilter添加响应头】######################################################
- id: payment_service8001_gatewayAddResponseHeader #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/gatewayAddResponseHeader
filters: #过滤器(filters:过滤器,过滤规则)
# 添加指定参数
- AddResponseHeader=X-Response-Red, Blue
########################################【Hystrix GatewayFilter断路器】######################################################
- id: payment_service8001_gatewayHystrixGatewayFilter #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/gatewayHystrixGatewayFilter
filters: #过滤器(filters:过滤器,过滤规则)
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
- id: payment_service8001_outerFallback-gatewayHystrix #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/gatewayOuterFallbackHystrixGatewayFilter
filters: #过滤器(filters:过滤器,过滤规则)
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/gatewayFallback #此处需要与下面- Path=/gatewayFallback对应
- id: outer-gateway-fallback
uri: http://localhost:8001
predicates:
#fallback时调用的方法 http://localhost:8001/gatewayFallback
- Path=/gatewayFallback
########################################【PrefixPath GatewayFilter路径前缀过滤器工厂】######################################################
- id: payment_service8001_prefixPathGatewayFilter #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/prefixPathGatewayFilter
filters: #过滤器(filters:过滤器,过滤规则)
#将把/api作为所有匹配请求的路径的前缀。因此,对/prefixPathGatewayFilter的请求将被发送到/api/prefixPathGatewayFilter
- PrefixPath=/api
########################################【StripPrefix GatewayFilter Factory路径截取过滤器工厂】######################################################
#如下配置表示,在访问localhost:9528/api/gatewayStripPrefix/**请求的,gateway网关将/api截取掉,请求分发到http://localhost:8001中去.
- id: payment_service8001_gatewayStripPrefix #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
# 转发地址格式为 uri/gatewayStripPrefix, /api 部分会被下面的过滤器给截取掉.
- Path=/api/gatewayStripPrefix/**
filters: #过滤器(filters:过滤器,过滤规则)
# 截取路径位数 即截取/api
- StripPrefix=1
########################################【RewritePath GatewayFilter Factory路径截取过滤器工厂】######################################################
#如下配置表示,在访问localhost:9528/api/rewritePathGatewayFilter/**请求的,gateway网关将路径重写为localhost:9528/rewritePathGatewayFilter,然后请求分发到http://localhost:8001中去.
- id: payment_service8001_rewritePathGatewayFilter #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/api/rewritePathGatewayFilter
filters: #过滤器(filters:过滤器,过滤规则)
- RewritePath=/api(?<segment>/?.*), $\{segment}
##########################################【测试自定义权限过滤器】#################################################
- id: payment_service8001_customAuthFilter #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/customAuthFilter/**
filters: #过滤器(filters:过滤器,过滤规则)
- CustomAuth
##########################################【自定义过滤器工厂】#################################################
- id: payment_service8001_customGatewayFilterFactory #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/customGatewayFilterFactory/**
filters: #过滤器(filters:过滤器,过滤规则)
- CustomRequestTime=true
##########################################【测试自定义全局过滤器】#################################################
- id: payment_service8001_customGatewayGlobalFilter #路由ID
uri: http://localhost:8001 #指定payment8001的访问地址,即匹配后提供服务的路由地址
predicates:
- Path=/customGatewayGlobalFilter/**
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
eureka:
instance:
hostname: springcloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://springcloud-eureka7001.com:7001/eureka/,http://springcloud-eureka7002.com:7002/eureka/ #集群版Eureka注册中心
【d】自定义全局过滤器:实现GlobalFilter, Ordered接口
package com.wsh.springcloud.filter;
import com.alibaba.fastjson.JSON;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.Refill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @version V1.0
* @ClassName: com.wsh.springcloud.filter.FluidControlGlobalGatewayFilter.java
* @Description: 自定义全局过滤器 - 限流过滤器(Token令牌桶算法)
* @author: weishihuai
* @date: 2020/8/22 11:30
*/
@Component
public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(FluidControlGlobalGatewayFilter.class);
/**
* 桶的最大容量,即能装载Token令牌的最大数量
*/
private int capacity = 5;
/**
* 每次Token补充量
*/
private int refillTokens = 1;
/**
* 补充Token的时间间隔
*/
private Duration duration = Duration.ofSeconds(1);
private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip,
k -> {
Refill refill = Refill.greedy(refillTokens, duration);
Bandwidth limit = Bandwidth.classic(capacity, refill);
return Bucket4j.builder().addLimit(limit).build();
});
logger.info("客户端IP地址: " + ip + ", 获取: " + bucket.getAvailableTokens());
if (bucket.tryConsume(1)) {
//获取token成功,执行下一个过滤器逻辑
return chain.filter(exchange);
} else {
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> data = new HashMap<>();
data.put("code", HttpStatus.NOT_ACCEPTABLE);
data.put("msg", "超过访问次数!");
byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(datas);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
@Override
public int getOrder() {
return -10;
}
}
【e】测试
启动项目,浏览器访问:http://localhost:9528/customGatewayGlobalFilter/weishihuai,
网关成功转发请求,下面我们疯狂点击浏览器刷新按钮,手动触发令牌桶的限制,可见,再次访问时,已经被被限制访问,如下图:
后台日志:
2020-08-22 21:20:43.044 INFO 7320 --- [ctor-http-nio-3] c.w.s.f.FluidControlGlobalGatewayFilter : 客户端IP地址: 0:0:0:0:0:0:0:1, 获取: 0
(二)、自定义全局过滤器 - IP白名单过滤
当GlobalFilter的逻辑比较多时,建议大家单独写一个GlobalFilter来处理,比如我们要实现对IP的访问限制,不在IP白名单中就不让调用的需求,只需要实现GlobalFilter, Ordered这两个接口就可以了。
【a】自定义全局过滤器:实现GlobalFilter, Ordered接口
package com.wsh.springcloud.filter;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @version V1.0
* @ClassName: com.wsh.springcloud.filter.IpWhitelistGlobalGatewayFilter.java
* @Description: 自定义全局过滤器 - IP白名单过滤
* @author: weishihuai
* @date: 2020/8/22 12:03
*/
@Component
public class IpWhiteListGlobalGatewayFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(IpWhiteListGlobalGatewayFilter.class);
/**
* 白名单配置集合
* 此处为了演示方便,实际中需要采取配置的方式去配置白名单
*/
private static List<String> whiteList = new ArrayList<>();
static {
//localhost
whiteList.add("0:0:0:0:0:0:0:1");
//127.0.0.1
whiteList.add("127.0.0.1");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取发起请求的客户端IP地址
String ipAddress = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
//判断是否在配置的白名单中,存在则放行;不存在则拦截
if (whiteList.contains(ipAddress)) {
//获取token成功,执行下一个过滤器逻辑
return chain.filter(exchange);
} else {
logger.error("IP地址:{}未在白名单中,禁止访问!", ipAddress);
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> data = new HashMap<>();
data.put("code", HttpStatus.NOT_ACCEPTABLE);
data.put("msg", "当前IP地址未在白名单中,禁止访问!");
byte[] datas = JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(datas);
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
@Override
public int getOrder() {
return -1;
}
}
【b】测试
浏览器访问:http://localhost:9528/customGatewayGlobalFilter/weishihuai
浏览器访问:
如上,可见使用localhost或者127.0.0.1访问的时候,由于都在配置的IP白名单中,所以这些访问过滤器将会放行。然后我们继续访问:http://192.168.1.12:9528/customGatewayGlobalFilter/weishihuai
由于192.168.1.12不在IP白名单中,所以请求被禁止访问。
三、总结
以上就是关于在Gateway中如何自定义全局过滤器的总结,以上相关项目的代码我已经放在Gitee上,有需要的小伙伴可以去拉取进行学习:https://gitee.com/weixiaohuai/springcloud_Hoxton,由于笔者水平有限,如有不对之处,还请小伙伴们指正,相互学习,一起进步。