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

Stella981
• 阅读 1301

一、前言

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版本)
    网图借鉴度娘上的文章: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. 访问Eureka

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

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

a. 不传Token参数

a. 传Token参数

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

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

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

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

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

七、源码

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

八、SpringCloud系列教程

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

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

本文同步分享在 博客“Thinkingcao”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
SpringCloud系列——Zuul 动态路由
  前言  Zuul是在SpringCloudNetflix平台上提供动态路由,监控,弹性,安全等边缘服务的框架,是Netflix基于jvm的路由器和服务器端负载均衡器,相当于是设备和Netflix流应用的Web网站后端所有请求的前门。本文基于上篇(SpringCloud系列——Ribbon负载均衡(https://www.
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这