Spring Cloud Zuul 过滤器

Spring Cloud Zuul 除了能够实现请求的路由功能,还有一个重要的功能就是过滤器。Zuul 的路由功能让全部的微服务提供的接口有统一的网关入口,但并非全部的接口都是对外彻底开发的,它们的访问权限通常都有必定的限制。那咱们能够在每一个服务都加上对应的校验和权限鉴定,那这些一般都是用过滤器或拦截器实现的,并且一个系统的各个服务的校验也大都是类似,这些类似的校验逻辑打码在每一个服务都会有一份,不只冗余且维护麻烦。更好的办法就是在请求的最前端统一去作这样的事情,而统一的 API 服务网关入口就是合适的选择前端

Zuul 能够经过定义过滤器来实现请求的拦截和过滤,而它自己的大部分功能也是经过过滤器实现的java

过滤器

在 Zuul 中自定义过滤器须要继承抽象类 ZuulFilter ,须要实现如下4个方法:git

String filterType(); // 过滤器类型

int filterOrder(); // 执行顺序,数值越小优先级越高
                      
boolean shouldFilter(); // 执行过滤器的条件

Object run() throws ZuulException; // 具体的过滤操做

从名字咱们也能知道各个方法的做用,下面来了解下过滤器类型和过滤器的生命周期,以及自定义过滤器的使用github

过滤器类型和生命周期

Zuul 定义了4种不一样的过滤器类型,对应着请求的典型生命周期web

  • PRE: 在请求被路由以前执行。能够用于请求身份验证、选择源服务器和记录调试信息
  • ROURING: 该过滤器将请求路由到服务。用于构建和发送给微服务的请求(使用 Apache HttpClient 或 Netflix Ribbon 请求服务)
  • POST: 在请求被路由到服务以后执行。能够用于向响应添加标准的 HTTP Header、收集统计数据和指标,以及将响应从源服务发送到客户端
  • ERROR: 该过滤器在其余阶段发生错误时执行

除了默认的过滤器流,Zuul 还容许咱们建立自定义过滤器类型并显式地执行它们。例如,咱们能够自定义一个 STATIC 类型的过滤器,它在Zuul 中生成响应,而不是将请求转发到后端的服务spring

下面是 Zuul 的生命周期图,描述着各类类型的过滤器的执行顺序(图片来源于 Zuul Wikijson

Zuul 的生命周期图

Spring Cloud Zuul 的过滤器

Spring Cloud Zuul 做为服务网关的大部分功能都是经过过滤器实现的,它在请求的各个阶段实现了一系列的过滤器,在 Spring Cloud Zuul 网关服务启动时自动加载和启用。实现的这些过滤器是在 spring-cloud-netflix-zuul 模块中的 org.springframework.cloud.netflix.zuul.filters 包下面后端

下面介绍部分过滤器的功能服务器

Pre filtersapp

filter order 说明
ServletDetectionFilter -3 检测请求是否经过 Spring 调度程序,即判断请求是交由 Spring DispatcherServlet 处理,仍是 ZuulServlet 处理(主要是用于大文件上传)
Servlet30WrapperFilter -2 把原始 HttpServletRequest 包装成 Servlet30RequestWrapper 对象
FormBodyWrapperFilter -1 解析表单数据并为下游服务从新编码
DebugFilter 1 若是设置 debug 请求参数,则此过滤器将RequestContext.setDebugRouting() 和 RequestContext.setDebugRequest() 设置为true
PreDecorationFilter 5 根据提供的 RouteLocator 肯定路由的位置和方式,它还为下游请求设置各类与代理相关的头文件

Route filters

filter order 说明
RibbonRoutingFilter 10 使用 Ribbon、Hystrix 和 可插拔 HTTP客户机发送请求。只对 RequestContext 存在 serviceId 参数的请求进行处理,即只对经过 serviceId配置路由规则的请求路由。可使用不一样的 HTTP 客户端:HttpClient、OkHttpClient、Netflix Ribbon HTTP client
SimpleHostRoutingFilter 100 经过 Apache HttpClient 发送请求到预约的 url,这些 url 能够在 RequestContext.getRouteHost() 中找到,即只对经过 url 配置路由规则的请求路由
SendForwardFilter 500 经过使用 Servlet RequestDispatcher 转发请求。用于转发请求到当前应用的端点

Post filters

filter order 说明
LocationRewriteFilter 900 负责将 Location header 重写为 Zuul URL
SendResponseFilter 1000 将代理请求的响应写入当前响应

Error filters

filter order 说明
SendErrorFilter 0 利用请求上下文中的错误信息来组织成一个 forward 到 /error 错误端点的请求来产生错误响应

禁用过滤器

默认状况下,这些过滤器在代理和服务器模式下都是启用的。若是在某些场景下,禁用某个过滤器,能够设置 zuul.<SimpleClassName>.<filterType>.disable=true。例如,要禁用org.springframework.cloud.netflix.zuul.filter.post.sendresponsefilter,设置 zuul.SendResponseFilter.post.disable=true

自定义过滤器

建立一个 Spring Boot 项目 zuul-filters,Zuul 的服务网关路由配置能够见 Spring Cloud Zuul 构建微服务网关

spring:
  application:
    name: zuul-filters
server:
  port: 8090
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

zuul:
  ignoredServices: '*'
  routes:
    product:
      path: /product/**
      serviceId: product-service

management:
  endpoints:
    web:
      exposure:
        include: '*'

自定义一个过滤器 AccessFilter,对请求中没有 accessToken 参数的请求,返回 401拒绝访问

@Log4j2
public class AccessFilter extends ZuulFilter {

    @Override
    public int filterOrder() {
        // run before PreDecoration
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // a filter has already forwarded
        // a filter has already determined serviceId
        return !ctx.containsKey(FilterConstants.FORWARD_TO_KEY)
                && !ctx.containsKey(FilterConstants.SERVICE_ID_KEY);
    }

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

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

        String token = request.getParameter("accessToken");
        if(StringUtils.isBlank(token)) {
            log.warn("access token is empty");
            // 过滤该请求,不对其进行路由
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            return null;
        }
        log.info("access token ok");
        return null;
    }
}

实现自定义过滤器后,把它添加到 Spring 的 Beans 中

@SpringBootApplication
@EnableZuulProxy
public class ZuulFiltersApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulFiltersApplication.class, args);
    }

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

下面咱们来测试下结果,启动项目 eureka-serverproduct-serivce(做为代理的服务)、zuul-filters

访问 http://localhost:8090/product/product/1 返回 401
访问 http://localhost:8090/product/product/1?accessToken=111 会正常路由到 product-service 的 /product/1

参考代码见:demo

过滤器管理端点

@EnableZuulProxy 注解配合 Spring Boot Actuator,Zuul 会暴露额外的两个管理端点:RoutesFilters。分别是关于路由和过滤器的端点(服务路由的端点在这里介绍 Spring Cloud Zuul 构建微服务网关

spring-cloud-starter-netflix-zuul 已经依赖了 spring-boot-starter-actuator,因此上面的工程已经包含了路由管理的功能。关于过滤器的管理端点的路径为 /filters

访问路径 http://localhost:8090/actuator/filters

{
  "error": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter",
      "order": 0,
      "disabled": false,
      "static": true
    }
  ],
  "post": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",
      "order": 1000,
      "disabled": false,
      "static": true
    }
  ],
  "pre": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter",
      "order": 1,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter",
      "order": -1,
      "disabled": false,
      "static": true
    },
    {
      "class": "com.turbosnail.zuul.filter.AccessFilter",
      "order": 4,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter",
      "order": -2,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter",
      "order": -3,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter",
      "order": 5,
      "disabled": false,
      "static": true
    }
  ],
  "route": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter",
      "order": 100,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",
      "order": 10,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter",
      "order": 500,
      "disabled": false,
      "static": true
    }
  ]
}

访问404是由于没有暴露端点,能够设置 management.endpoints.web.exposure.include: '*'

相关文章
相关标签/搜索