网关服务-3-过滤器

每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都需要有一定的限制,系统并不会将所有的微服务接口都对它们开放。然而,目前的服务路由并没有限制权限这样的功能,所有请求都会被毫无保留地转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。不过,这样的做法并不可取,它会增加日后的系统维护难度,因为同一个系统中的各种校验逻辑很多情况下都是大致相同或类似的,这样的实现方式会使得相似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是我们不希望看到的。所以,比较好的做法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。在完成了剥离之后,有不少开发者会直接在微服务应用中通过调用鉴权服务来实现校验,但是这样的做法仅仅只是解决了鉴权逻辑的分离,并没有在本质上将这部分不属于业余的逻辑拆分出原有的微服务应用,冗余的拦截器或过滤器依然会存在。

对于这样的问题,更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统已经有了统一入口,既然这些校验与具体业务无关,那何不在请求到达的时候就完成校验和过滤,而不是转发后再过滤而导致更长的请求延迟。同时,通过在网关中完成校验和过滤,微服务应用端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用的接口开发和测试复杂度也得到了相应的降低。

为了在API网关中实现对客户端请求的校验,我们将需要使用到Spring Cloud Zuul的另外一个核心功能:过滤器。

Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,实现的方法非常简单,我们只需要继承ZuulFilter抽象类并实现它定义的四个抽象函数就可以完成对请求的拦截和过滤了。

  • 1.过滤器的实现
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

    public class AccessFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public String filterType() {
    return "pre";
    }

    @Override
    public int filterOrder() {
    return 0;
    }

    @Override
    public boolean shouldFilter() {
    return true;
    }

    @Override
    public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();

    log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());

    Object accessToken = request.getParameter("accessToken");
    if(accessToken == null) {
    log.warn("access token is empty");
    ctx.setSendZuulResponse(false);
    ctx.setResponseStatusCode(401);
    return null;
    }
    log.info("access token ok");
    return null;
    }

    }

以上是一个过滤器的简单实现,实现了在请求被路由之前,会检测HttpServletRequest中是否有accessToken参数,对于没有这个参数的请求直接报错,拒绝进行路由。
实现一个过滤去主要是通过继承ZuulFilter抽象类重写其中的四个方法来实现:

  • 1.filterType:定义当前过滤器的类型,主要是决定当前的过滤器会在那个生命周期中执行,上文中定义为pre,表示在路由之前被执行
  • 2.filterOrder:定义了当前过滤器执行的顺序,当在一个请求阶段中存在多个过滤器时,需要根据当前方法的返回值来按序进行执行过滤器
  • 3.shouldFilter:判断是否需要执行当前过滤器,文中直接返回true,该过滤器会对所有的请求生效。
  • 4.run:过滤器的具体逻辑实现

在定义了过滤器之后不会自动的生效,需要我们为其创建具体的bean才能启动该过滤器,例如在主类中加入一下

1
2
3
4
5
6
7
8
9
10
11
12
13
@EnableZuulProxy
@SpringCloudApplication
public class Application {

public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}

@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}

在定义了上面的bean之后该过滤器就会在当前服务中生效,过滤请求中没有token的请求路由操作。