Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)

一、前言

Zuulnetflix开源的一个API网关服务器, 其本质上是一个web servlet应用。Zuul 是在云平台上提供动态路由监控弹性安全等边缘服务的框架。Zuul相当于是PCAPPH5等客户端和 Netflix 流应用的 Web网站后端所有请求的前门,ZuulSpring Cloud提供的api网关和过滤组件,网关将所有服务的API接口统一聚合,统一对外暴露。外界调用API接口时,不需要知道微服务系统中各服务相互调用的复杂性和具体的API接口调用地址,保护了内部微服务单元的API接口;网关可以做用户身份认证和权限认证,防止非法请求操作API接口;网关可以实现监控功能,实时日志输出,对请求进行记录;网关可以实现流量监控,在高流量的情况下,对服务降级;API接口从内部服务分离出来,方便做测试。
总而言之Zuul具有以下功能:

  • 认证
  • 过滤
  • 压力测试
  • Canary测试
  • 动态路由
  • 服务迁移
  • 负载均衡
  • 安全
  • 静态请求处理
  • 动态流量管理

二、Zuul网关的作用

网关通常可以做的事情有以下几个点:

  • API服务的统一入口: 为全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
  • 鉴权校验、验证登录: 识别每个请求的权限,拒绝不符合要求的请求。
  • 动态路由: 动态的将请求路由到不同的后端集群中。
  • 减少客户端与服务端的耦合: 服务可以独立发展,通过网关层来做映射。
  • 日志拦截: 进行核心日志统一拦截跟踪记录。
  • 解决跨域: 前后端分离常见跨域问题。
  • 反向代理: 不暴露具体API服务的接口调用真实IP地址。
  • 黑名单、白名单过滤: 实现黑名单和白名单过滤、管理。
  • 负载均衡: 实现对服务负载均衡。
  • 限流熔断: 服务限流、熔断降级隔离处理
    Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第1张图片
    网图借鉴度娘上的文章:SpringCloud之Zuul网关原理及其配置

三、Nginx与Zuul的区别

  • Nginx是采用服务器负载均衡进行转发
  • Zuul是依赖Ribboneureka实现本地负载均衡转发
  • 二者相对来说Nginx功能比Zuul功能更加强大,能够整合其他语言比如lua脚本实现强大的功能
  • Nginx可以更好的抗高并发,Zuul网关适用于请求过滤和拦截等。

SpringCloud中想要使用Zuul网关服务组件的话,只需引入一个依赖组件即可:

 <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
 </dependency>

Zuul本质上是通过Servlet来实现,通过自定义的ZuulServlet来对请求进行控制。核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列过滤器。Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。

四、搭建Zuul网关拦截请求头Token鉴权

基于前面第四节搭建的环境,我们继续使用前面搭建好的工程,作为本章学习:

  • eureka注册中心工程: springcloud-eureka-server
  • 服务提供者工程: springcloud-app-member
  • 服务消费者工程: springcloud-feign-client

然后我们再搭建一个基于Zuul的网关路由服务工程,Zuul网关单独作为一个微服务工程模块,因此我们再新建一个项目springcloud-zuul_gateway,然后在Zuul网关服务里实现Zuul网关拦截请求头的Token参数,实现鉴权,没有传递token参数在请求头的话,则返回401,无权操作的结果,如果有token参数,则直接放行去执行其他业务逻辑代码,这个过程由Feign客户端调用其他微服务接口。

五、搭建环境

1. 引入springcloud-feign-client工程依赖

  <!--SpringBoot依赖版本-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.thinkingcao</groupId>
    <artifactId>springcloud-zuul_gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springcloud-zuul_gateway</name>
    <description>SpringCloud整合Zuul实现APi网关拦截请求</description>

    <!--项目编码、jdk版本、SpringCloud版本定义-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <!--声明管理SpringCloud版本依赖信息-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- SpringBootWeb组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- springcloud整合eureka客户端组件   -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- SpringCloud整合Zuul APi网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

    <!--maven插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

2.配置application.yml文件

关于zuul配置文件信息那一块,application.yml里写的很详细了,这里就不在做过多解释,如果不清楚的可以在文章末尾评论,我每天都会看,回复给你们;

##服务端口配置
server:
  port: 9999

#定义网关服务名称(服务注册到eureka名称)
spring:
  application:
    name: zuul-gateway-service
  #解决响应给客户端浏览器中文乱码问题
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true


#在此指定服务注册中心地址,将当前订单服务注册到eureka注册中心上
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9000/eureka
    #启动注册操作,该值默认为true。若设置为fasle将不会启动注册操作。是否需要去检索寻找服务,默认是true
    register-with-eureka: true
    #是否需要从eureka上获取注册信息
    fetch-registry: true
    #表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
    registry-fetch-interval-seconds: 10

  ##心跳检测与续约时间(测试环境和本地开发环境将值设置小一点,保证服务关闭后,注册中心能够及时踢出)
  instance:
    #客户端向Eureka注册中心发送心跳的时间间隔,单位为秒(默认为30s),(客户端会按照此规则向Eureka服务端发送心跳检测包)
    lease-renewal-interval-in-seconds: 2
    #Eureka注册中心在收到客户端最后一次心跳之后等待的时间上限,单位为秒(默认为90s),超过时间则剔除(客户端会按照此规则向Eureka服务端发送心跳检测包)
    lease-expiration-duration-in-seconds: 2

##配置Zuul网关反向代理
zuul:
  routes:
    #定义Zuul网关转发的服务规则api-a和api-b等(这个规则名可以随便取)
    api-a:
      #以/api-member/ 的访问请求, URL: 127.0.0.1:9999/zuul-api-member/ ,会转发到会员服务(服务提供者)
      path: /zuul-api-member/**
      # 指定服务别名,表示需要转发到哪个服务
      serviceId: app-thinkingcao-member
    #定义Zuul网关转发的服务规则api-a
    api-b:
      # 以/api-order/ 的访问请求, URL: 127.0.0.1:9999/zuul-api-order/, 会转发到订单服务(服务消费者)
      path: /zuul-api-order/**
      # 指定服务别名,表示需要转发到哪个服务
      serviceId: app-thinkingcao-feign

3.修改ZuulGatewayApp启动类

  • @EnableZuulProxy: 表示开启Zuul网关代理功能
  • @EnableEurekaClient: 表示开启Eureka客户端像Eureka服务端注册与发现服务功能
  • @EnableZuulServer: ,另外还有个@EnableZuulServer注解,这里解释下:@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当ZuulEurekaRibbon等组件配合使用时,我们使用@EnableZuulProxy
package com.thinkingcao.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulGatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(ZuulGatewayApp.class, args);
    }
}

4.编写AuthorizationFilter鉴权请求获取Token

package com.thinkingcao.api.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @desc: 网关Filter过滤器
 * @author: cao_wencao
 * @date: 2020-03-02 11:47
 */
@Component
public class AuthorizationFilter extends ZuulFilter {

    //指定过滤器的类型
    // pre :可以在请求被路由之前调用
    // route :在路由请求时候被调用
    // post :在route和error过滤器之后被调用
    // error :处理请求时发生错误时被调用
    @Override
    public String filterType() {
        return "pre";
    }

    //过滤器的执行顺序,数值越小,优先级越高。当一个请求在同一阶段的时候存在多个过滤器的时候,通过数值表示过滤器的执行顺序
    @Override
    public int filterOrder() {
        return 0;
    }

    //是否执行该过滤器,true 为执行,false 为不执行,这个也可以利用配置中心来实现,达到动态的开启和关闭过滤器。
    @Override
    public boolean shouldFilter() {
        return true;
    }
    
    //执行自己的业务逻辑,编写业务逻辑代码
    @Override
    public Object run() throws ZuulException {
        // 案例:拦截所有的服务接口,判断所有的APi请求,请求头中是否有携带Token参数,用Authorization字段传递
        // 1.获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        // 2.获取 Request
        HttpServletRequest request = currentContext.getRequest();
        // 3.获取token 的时候 从请求头中获取
        String token =   currentContext .getRequest().getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            // 不会继续执行... 不会去调用服务接口,网关服务直接响应给客户端
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseBody("Token参数为空,请先登录后再执行操作");
            currentContext.setResponseStatusCode(401);
             //解决响应给客户端浏览器中文乱码问题
            currentContext.getResponse().setContentType("text/html;chatset=utf-8");
            return null;
            // 返回一个错误提示
        }
        // 正常则执行调用其他服务接口...
        return null;
    }
}

六、开始测试

1. 依次启动服务

AppEurekaServer(注册中心)——>AppMemberProvider(服务提供者)——>AppFeignClient(服务消费者)——>ZuulGatewayApp(网关服务)
Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第2张图片

2. 访问Eureka

Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第3张图片

3. 通过网关入口调用会员服务

a. 不传Token参数

  • URL: http://127.0.0.1:9999/zuul-api-member/getMember
    Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第4张图片
    a. 传Token参数

    • URL: http://127.0.0.1:9999/zuul-api-member/getMember
    • Authorization里面放入token的值Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第5张图片

    4. 通过Feign客户端消费服务

    a. 不传Token参数

    • URL: http://127.0.0.1:9999/zuul-api-order/getOrderToMemberInfo?userName=“Thinkingcao”
      Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第6张图片

a. 传Token参数

  • URL: http://127.0.0.1:9999/zuul-api-order/getOrderToMemberInfo?userName=“Thinkingcao”
    Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第7张图片

5. 基于Feign消费服务实现本地负载均衡效果

为了实现负载均衡的效果,上面已经开启了4个微服务工程,这个时候我们将服务提供者AppMemberProvider设置为允许启动多个实例,然后再启动一个端口为8767的实例,这就实现了服务提供者的集群效果,如下:
Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第8张图片
a. 传Token参数

  • URL: http://127.0.0.1:9999/zuul-api-order/getOrderToMemberInfo?userName=“Thinkingcao”

Postman工具交替显示响应结果如下:

我是会员服务,订单服务调用会员服务成功啦, 端口号为: 8765
我是会员服务,订单服务调用会员服务成功啦, 端口号为: 8767

Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第9张图片
Spring Cloud系列教程(九):服务网关Zuul(Finchley版本)_第10张图片

七、源码

1. 源码: https://github.com/Thinkingcao/SpringCloudLearning/tree/master/springcloud-zuul

八、SpringCloud系列教程

1. 下一节: Spring Cloud系列教程(十):分布式配置中心Config(Finchley版本)

SpringCloud教程汇总: Spring Cloud系列教程(汇总篇):专栏汇总篇(持续更新中)

你可能感兴趣的:(Spring,Cloud2.x系列教程)