• SpringCloud搭建微服务之Zuul网关


    1. 概述

    Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务,是一个基于JVM路由和服务端的负载均衡器,在Spring Cloud框架中,Zuul的角色是网关,负责接收所有REST请求,然后进行内部转发,是微服务提供者集群的流量入口。

    1.1. 主要功能

    路由:将不同REST请求转发至不同的微服务提供者,其作用类似于Nginx的反向代理。同时,也起到了统一端口的作用,将很多微服务提供者的不同端口统一到了Zuul的服务端口
    认证:网关直接暴露在公网上时,终端要调用某个服务,通常会把登录后的token(令牌)传过来,网关层对token进行有效性验证。如果token无效(或没有token),就不允许访问REST服务。可以结合Spring Security中的认证机制完成Zuul网关的安全认证
    限流:高并发场景下瞬时流量不可预估,为了保证服务对外的稳定性,限流成为每个应用必备的一道安全防火墙。如果没有这道安全防火墙,那么请求的流量超过服务的负载能力时很容易造成整个服务的瘫痪
    负载均衡:在多个微服务提供者之间按照多种策略实现负载均衡

    2. 搭建Zuul网关服务

    Zuul作为网关层微服务,跟其他服务提供者一样都注册在Eureka Server上,可以相互发现。Zuul能感知到哪些服务Provider实例在线,同时通过配置路由规则可以将REST请求自动转发到指定的后端微服务提供者

    2.1. 引入核心依赖

    <dependency>
      <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-zuulartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.2. 编写主启动类

    @EnableZuulProxy
    @SpringBootApplication
    public class ZuulApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在启动类中添加注解@EnableZuulProxy,声明这是一个网关服务提供者

    2.3. 编写application.yml基础配置

    server:
      port: 8810
    spring:
      application:
        name: cloud-zuul
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8761/eureka
      instance:
        prefer-ip-address: true
        ip-address: ${spring.cloud.client.ip-address}
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
    zuul:
      routes:
        cloud-provider:
          path: /cloud-provider/**
          serviceId: cloud-provider
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.4. 验证

    依次启动服务Eureka Server、Provider和Zuul,在浏览器地址输入http://localhost:8770/provider/getProviderInfo/world
    直接访问服务提供者
    再次在浏览器地址栏输入http://localhost:8810/cloud-provider/provider/getProviderInfo/world
    直接访问zuul网关

    3. 路由规则配置

    路由规则通常有两种方式,其一是路由到直接URL,其二是路由到微服务提供者
    两种方式的区别如下:

    • 第一种方式使用url属性来指定直接的上游URL的前缀,第二种方式使用serviceId属性来指定上游服务提供者的名称
    • 第二种方式需要结合Eureka Client客户端来实现动态的路由转发功能,需要配置Eureka相关配置信息

    3.1. 过滤敏感请求头部

    防止请求头泄露的方式之一是,在Zuul的路由配置中指定要忽略的请求头列表,并且多个敏感头部之间可以用逗号隔开,默认情况,Zuul转发请求会把header清空,如果在微服务集群内部转发请求,上游Provider就会收不到任何头部,如果需要传递原始的header信息到最终的上游,就需要添加如下敏感头部设置

    zuul:
      routes:
        cloud-provider:
          sensitiveHeaders: 
    
    • 1
    • 2
    • 3
    • 4

    如何需要屏蔽头信息,需要如下配置

    zuul:
      routes:
        cloud-provider:
          sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
    
    • 1
    • 2
    • 3
    • 4

    3.2. 路径前缀处理

    默认情况下Zuul会去掉路由的路径前缀,如果上游微服务提供者没有配置路径前缀,Zuul这种默认处理和转发就不会有问题,如果上游微服务提供者配置了统一的路径前缀,前缀去掉后,上游服务提供者就会报404错误,找不到URL对应的资源服务。可以设置配置项stripPrefix的值为false

    zuul:
      routes:
        cloud-provider:
          path: /cloud-provider/**
          serviceId: cloud-provider
          stripPrefix: true #是否取消请求前缀
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果需要对访问网关的所有请求都加上前缀,可以设置配置prefix,具体配置如下:

    zuul:
      prefix: /native #所有请求添加前缀
      routes:
        cloud-provider:
          path: /cloud-provider/**
          serviceId: cloud-provider
          stripPrefix: true #是否取消请求前缀
          sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4. 查看路由信息

    4.1. 引入依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    4.2. 开启路由监控端口

    management:
      endpoints:
        web:
          exposure:
            include: 'routes' #开启查看路由端口
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.3. 验证

    重新启动服务,在浏览器地址栏输入http://localhost:8810/actuator/routes
    路由监控

    5. 过滤器

    通过定义过滤器来实现请求的拦截和过滤

    5.1. 过滤器类型

    pre类型过滤器
    请求路由之前调用,用于实现身份验证、记录调试信息
    route类型过滤器
    发送请求到上游服务,例如使用Apache HttpClient或Netflix Ribbon请求上游服务
    post类型过滤器
    上游服务返回之后调用,为响应添加HTTP响应头、收集统计信息和指标、将响应回复给客户端
    error类型过滤器
    在其他阶段发生错误时执行

    5.2. 请求处理流程

    1. 当外部请求到达Zuul网关时,首先会进入pre处理阶段,在这个阶段请求将被pre类型的过滤器处理,以完成再请求路由的前置过滤处理,比如请求的校验等。在完成pre类型的过滤处理之后,请求进入第二个阶段:route路由请求转发阶段
    2. 在route路由请求转发阶段,请求将被route类型的过滤器处理,route类型的过滤器将外部请求转发到上游的服务。当服务实例的结果返回之后,route阶段完成,请求进入第三个阶段:post处理阶段
    3. 在post处理阶段,请求将被post类型的过滤器处理,post类型的过滤器在处理的时候不仅可以获取请求信息,还能获取服务实例的返回信息,所以post阶段可以对处理结果进行一些加工或转换等
    4. 还有一个特殊的阶段error,在该阶段请求将被error类型的过滤器处理,在上述3个阶段发生异常时才会触发,但是error过滤器也能将最终结果返回给请求客户端
      Zuul请求处理流程

    5.3. 实例

    Zuul提供一个过滤器ZuulFilter抽象基类,可以作为自定义过滤器的父类,需要实现的方法主要有4个
    filterType方法
    返回自定义过滤器类型,以常量的形式定义在FilterConstants类中
    filterOrder方法
    返回过滤器顺序,值越小优先级越高
    shouldFilter方法
    返回过滤器是否生效,返回true表示生效,返回false表示不生效
    run方法
    过滤器业务逻辑处理,可以进行当前的请求拦截和参数定制,后续路由定制,返回结果定制
    例如可以使用前置过滤器打印日志和黑名单处理过滤,具体代码如下

    @Component
    public class BlackListFilter extends ZuulFilter {
    
        private static final Logger logger = LoggerFactory.getLogger(BlackListFilter.class);
        static List<String> blackList = Arrays.asList("");
    
        /**
         * 过滤器类型pre:过滤之前;routing:路由之时;post:路由之后;error:发送错误调用
         * @return
         */
        @Override
        public String filterType() {
            return "pre";
        }
    
        /**
         * 过滤执行次序
         * @return
         */
        @Override
        public int filterOrder() {
            return 0;
        }
    
        /**
         * 是否执行过滤
         * @return
         */
        @Override
        public boolean shouldFilter() {
            RequestContext context = RequestContext.getCurrentContext();
            if (!context.sendZuulResponse()) {
                return false;
            }
            //返回true,表示需要执行run方法
            HttpServletRequest request = context.getRequest();
            if (request.getRequestURI().startsWith("/native")) {
                return true;
            }
            return false;
        }
    
        /**
         * 过滤器具体执行方法
         * @return
         * @throws ZuulException
         */
        @Override
        public Object run() throws ZuulException {
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
            String host = request.getRemoteHost();
            String method = request.getMethod();
            String uri = request.getRequestURI();
            logger.info("=====>Remote host:{},method:{},uri:{}", host, method, uri);
            String username = request.getParameter("username");
            if (null != username && blackList.contains(username)) {
                logger.info(username + " is forbidden: " + request.getRequestURL().toString());
                context.setSendZuulResponse(false);
                try {
                    context.getResponse().setContentType("text/html;charset=utf-8");
                    context.getResponse().getWriter().write("对不起,您已进入黑名单!");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
            return null;
        }
    }
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
  • 相关阅读:
    python在NumPy数组中查找等于零的元素索引
    Android——ScrollView与ConstraintLayout约束布局失效场景以及方案
    2022NUSTCTF--web
    Map 接口和常用方法
    机器学习(三):朴素贝叶斯+贝叶斯估计+BP人工神经网络习题手算|手工推导与习题计算
    我居然不知道Vue3可以使用hooks函数实现代码复用?
    轻松整理文件夹,将视频文件全部归类到另一个文件夹!
    Qt10-19
    三种常见的存储类型
    JNI查漏补缺
  • 原文地址:https://blog.csdn.net/liu320yj/article/details/126640949