Zuul请求过滤

在介绍完 Zuul 的核心功能之一的路由之后,我再介绍另一个核心功能,请求过滤。在这个功能中,常见的使用场景是鉴权以及限流。本节会对鉴权与限流做一个介绍。

应用场景

在开始介绍请求过滤功能之前,首先需要清楚过滤器的结构,对过滤的流程有一个直观的理解,这样对于后面的过滤程序能更好地理解。

过滤器结构

图15.10是 Zuul 过滤器结构,主要包含了 pre、post、route、error 类型的过滤器。

image 2024 04 01 13 46 28 387
Figure 1. 图15.10 过滤器结构

应用场景

下面是最常见的应用场景。

前置 pre:用于限流、鉴权、参数校验调整。

后置 post:用于统计、生成日志。

鉴权

为了验证 Zuul 的功能,我们还是通过示例进行说明。

pre过滤实例

定义一个简单的过滤器,在请求被路由之前先检查 HttpServletRequest 中是否存在 token 参数。如果存在,则进行路由,否则拒绝访问,返回 401。首先,新建一个 filter 包,然后在包下新建一个类,代码如下所示。

package com.cloudtest.gateway.filter;
@Component
public class TokenFilter extends ZuulFilter {
   @Override
   public String filterType() {
      return PRE_TYPE;
   }
   @Override
   public int filterOrder() {
      return PRE_DECORATION_FILTER_ORDER-1;
   }
@Override
public boolean shouldFilter() {
   return true;
}
@Override
public Object run() throws ZuulException {
   RequestContext requestContext=RequestContext.getCurrentContext();
   HttpServletRequest request=requestContext.getRequest();
   String token=request.getParameter(“token”);
   if(StringUtils.isEmpty(token)){
      requestContext.setSendZuulResponse(false);
      requestContext.setResponseStatusCode(401);
   }
   return null;
   }
}

在上面的代码中,需要继承 ZuulFilter 抽象类,并重写几个方法,下面对这几个方法进行说明。

  • filterType:过滤的类型。这个方法决定过滤器存在请求的哪个生命周期。在这里,要在请求被路由之前,进行过滤,所以选择使用 pre。

  • filterOrder:这个方法返回执行顺序的数字,这个数字越小,则过滤器越小。建议使用 FilterConstants 中的变量来减。

  • shouldFilter:判断这个过滤器是否需要被执行,在这里明显需要设置为 true。

  • run:这个方法是写逻辑的部分。

还有最后一点,这个过滤器 TokenFilter 写完不会生效,我们需要在类上添加注解 @Component,使得过滤器生效。访问链接 http://localhost:8066/myconsumerService/consumer?number=2&token=22 ,这样才可以进行路由转发。

post过滤实例

在 filter 包下,新建一个类,代码如下所示。

package com.cloudtest.gateway.filter;
//需要添加到IOC容器中
@Component
public class AddResponseFilter extends ZuulFilter {
   @Override
   public String filterType() {
      return POST_TYPE;
   }
   @Override
   public int filterOrder() {
      return SEND_RESPONSE_FILTER_ORDER-1;
   }
   @Override
   public boolean shouldFilter() {
      return true;
   }
   @Override
    public Object run() throws ZuulException {
      //获取RequestContext
      RequestContext requestContext=RequestContext.getCurrentContext();
      HttpServletResponse response=requestContext.getResponse();
      response.setHeader("X-Foo", UUID.randomUUID().toString());
        return null;
   }
}

上面的代码,使用的类型是 post。通过 RequestContext 获取 context,然后在里面再获取返回结果,最后在返回结果中添加 header。

访问链接 http://localhost:8066/myconsumerService/consumer?number=2&token=22 ,然后,观察结果,如图15.11所示。

image 2024 04 01 13 50 18 189
Figure 2. 图15.11 执行结果

限流

因为每个服务请求都会经过网关,所以比较适合做限流保护,防止网络攻击。这个限流也是在前置过滤器中,具体来说是在请求被转发之前被调用,而且限流还应该早于鉴权过滤器。

使用的算法是令牌桶算法。继续在 filter 包下新建类,代码如下所示。

package com.cloudtest.gateway.filter;
public class RateFilter extends ZuulFilter {
   private static final RateLimiter RATE_LIMITER=RateLimiter.create(100);
@Override
//过滤器类型
   public String filterType() {
      return PRE_TYPE;
   }
@Override
//过滤器执行顺序
   public int filterOrder() {
      return SERVLET_DETECTION_FILTER_ORDER-1;
   }
@Override
//是否执行
   public boolean shouldFilter() {
      return true;
   }
@Override
//逻辑运行方法
   public Object run() throws ZuulException {
      //没有拿到
      if(!RATE_LIMITER.tryAcquire()){
          throw new RuntimeException();
      }
      return null;
   }
}

这里对上面的代码做一个解释说明。限流的算法由 Google 的 RateLimiter 类提供,然后在 run 的逻辑中使用 tryAcquire 方法判断是否获取令牌。如果没有获取令牌,则抛出异常即可。我们在上面使用了 pre 前置过滤器,但是需要特别注意的是确定执行顺序,在这里需要选默认执行时间最早的值然后减一,做到最早执行。