Zuul服务网关

1、服务网关

服务网关是在微服务前边设置的一道屏障,请求先到服务网关,网关会对请求进行过滤、校验、路由等处理。有了服务网关可以提高微服务的安全性,网关校验请求的合法性,请求不合法将被拦截,拒绝访问。

简单来说,服务网关就是架设在前端和后端服务之间的一道关卡,提供了访问后端服务的唯一入口,前端的所有请求必须先经过服务网关过滤,然后过滤后的合法请求将会由服务网关路由到指定的微服务。

2、使用服务网关的理由

如果不使用服务网关,如下图:
在这里插入图片描述
所有请求由Nginx实现代理转发,如果微服务A、B、C、D都接入了SpringSecurity,那么所有请求都需要经过安全认证,由各个微服务自己来校验请求是否合法,这样其实挺麻烦的, 可以在微服务之前架设一个网关服务,由网关服务统一对所有请求进行校验,合法的请求再由网关服务路由到指定微服务,不合法的请求直接过滤掉,不会到达微服务,这样做的好处是缓解了下面各个服务端的压力,让各个服务专注于自己的核心业务,不用花费大量的资源在验证请求上。如下图:
在这里插入图片描述
加了服务网关之后,只有合法的请求才会到达服务端,不合法的请求由网关统一过滤拦截,不会到达。

3、Zuul

Zuul 是从设备和网站到应用程序后端的所有请求的前门。作为边缘服务应用程序,Zuul 旨在实现动态路由,监视,弹性和安全性。Zuul 包含了对请求的路由过滤两个最主要的功能。

Zuul 是 Netflix 开源的微服务网关,它可以和 Eureka、Ribbon、Hystrix 等组件配合使用。Zuul 的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产试图。
  • 动态路由:动态地将请求路由到不同的后端集群。
  • 压力测试:逐渐增加只想集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理:在边缘位置直接建立部份响应,从而避免其转发到内部集群。
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统的边缘更贴近系统的使用者。

Zuul主要是对请求进行过滤和路由的。Zuul与Nginx在实际项目中需要配合使用,Nginx的作用是反向代理、负载均衡,Zuul的作用是保障微服务的安全访问,拦截微服务请求,校验合法性及负载均衡。如下图:
在这里插入图片描述
下面进行简单的测试。

3.1、搭建Zuul网关服务

(1)创建maven工程
在这里插入图片描述
就一个filter包。

(2)pom依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath />
	</parent>

	<groupId>com.ycz</groupId>
	<artifactId>zuul-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>zuul-govern-gatewy</name>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
	</properties>

	<dependencies>
		<!-- Zuul依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>

		<!-- eureka依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>

		<!-- web模块 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<!-- 引入spring cloud -->
	<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>

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

</project>

(3)application.yml配置

server:
  port: 6000
  servlet:
    context-path: /api
    
spring:
  application:
    name: zuul-demo
    
## eureka配置
eureka:
  client:
    register-with-eureka: true  ## 打开服务注册开关
    fetch-registry: true  ## 打开服务发现开关
    service-url:
      defaultZone: http://localhost:20000/eureka/      
  instance:
    prefer-ip-address: true  ## 将自己的IP地址注册到eureka服务中
    ip-address: 127.0.0.1
    instance-id: ${spring.application.name}:${server.port} ## 实例名称
    
  ## zuul配置
zuul:
  routes:
    app1:
      path: /app1/**  ## 所有以app1开头的请求会路由到实例app1
      service-id: app1 ## 注册到eureka服务中心的名称为app1的实例
      strip-prefix: false  ## 转发时是否忽略前缀
      sensitive-headers: ##是否取消黑名单
    app2:
      path: /app2/**
      service-id: app2
      strip-prefix: false
      sensitive-headers:
      

(4)启动类

@SpringBootApplication
//表示这是一个网关代理服务
@EnableZuulProxy
public class ZuulApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }

}

注意@EnableZuulProxy是一个组合注解,已经包含了eureka客户端的注册注解。

(5)自定义过滤器

package com.ycz.zuul.gateway.filter;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

/*
 * 自定义过滤器
 * 需要继承ZuulFilter
 */
@Component
public class MyFilter extends ZuulFilter {

    @Override
    public boolean shouldFilter() {
        // 返回true表示该过滤器需要执行
        return true;
    }

    // 过滤的核心代码在这个方法里写
    @Override
    public Object run() throws ZuulException {
        // 获取上下文对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 获取请求对象
        HttpServletRequest request = requestContext.getRequest();
        System.out.println(request.getRequestURI().toString());
        // 取出头信息中的token
        String token = request.getHeader("Token");
        if (token == null || !token.equals("ycz123456")) {
            // 拒绝访问
            access_denied();
            return null;
        }
        return null;
    }

    @Override
    public String filterType() {
        // 请求在被路由之前执行
        return "pre";
    }

    @Override
    public int filterOrder() {
        // 数值表示过滤器的执行顺序,越小优先级越高
        return 0;
    }

    // 定义一个私有方法,拒绝访问
    private void access_denied() {
        // 获取上下文对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 获取响应对象
        HttpServletResponse response = requestContext.getResponse();
        response.setStatus(300);
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        PrintWriter writer;
        try {
            writer = response.getWriter();
            writer.write("{\"status\":\"error\",\"msg\":\"没有权限,请求已被网关拦截!\"}");
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

对以上4个重写方法的说明:

  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true表示要执行此过滤器,否则不执行。
  • run:过滤器的核心业务逻辑。
  • filterType:返回字符串代表过滤器的类型,pre表示请求在被路由之前执行,routing表示在路由请求时执行,post表示在routing和errror过滤器之后调用,error表示处理请求时发生错误调用。
  • filterOrder:此方法返回整型数值,通过此数值来定义过滤器的执行顺序,数字越小优先级越高。

以上一个简单的zuul网关工程就搭建完成了,最好先测试一下,看能不能跑得起来,我已经测过没问题,可以运行,运行之后控制台一直报错,这是因为eureka服务中心还没搭建,这个报错并不影响。

3.2、搭建eureka服务中心

Zuul一般会和eureka等组件配合使用,下面搭建eureka的服务中心。

(1)创建maven工程
在这里插入图片描述
这个工程基本是空的,只有一个启动类。

(2)pom依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath />
	</parent>

	<groupId>com.ycz</groupId>
	<artifactId>eureka-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
	</properties>

	<dependencies>
		<!-- eureka依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
	</dependencies>

	<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>

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

</project>

(3)application.yml配置

server:
  port: 20000
 
spring:
  application:
    name: eureka-server
    
## eureka配置
eureka:
  ## 客户端
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:20000/eureka/
  ## 服务端
  server:
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 600000
  ## 实例地址
  instance:
    hostname: 127.0.0.1

(4)启动类

@SpringBootApplication
//Eureka服务中心
@EnableEurekaServer
public class EurekaServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class,args);
    }

}

同样启动工程测试一下能不能跑起来,这边启动后再启动zuul服务,那边就不会报错了。

3.3、搭建资源服务

下面搭建两个简单的资源服务。

(1)创建maven工程
在这里插入图片描述
(2)pom依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath />
	</parent>

	<groupId>com.ycz</groupId>
	<artifactId>app1</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
	</properties>

	<dependencies>

		<!-- eureka客户端依赖 -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>

		<!-- web模块 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<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>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

(3)application.yml配置

server:
  port: 6010
    
spring:
  application:
    name: app1
    
## eureka配置
eureka:
  client:
    register-with-eureka: true ## 打开服务注册开关
    fetch-registry: true  ## 打开服务发现开关
    service-url:
      defaultZone: http://localhost:20000/eureka/      
  instance:
    prefer-ip-address: true  ## 将自己的IP地址注册到eureka服务中
    ip-address: 127.0.0.1
    instance-id: ${spring.application.name}:${server.port} ## 实例名称

(4)启动类

@SpringBootApplication
@EnableDiscoveryClient//eureka客户端
public class App1Application {
    
    public static void main(String[] args) {
        SpringApplication.run(App1Application.class, args);
    }

}

(5)控制器

@RestController
@RequestMapping("/app1")
public class App1Controller {
    
    @RequestMapping("/t1")
    public String t1() {
        return "成功访问到资源服务A的资源";
    }

}

就通过这个请求来获取资源服务的资源。

复制一份app1工程,重命名为app2,修改application.yml中的端口和名称:
在这里插入图片描述
再修改一下控制器里的内容:

@RestController
@RequestMapping("/app2")
public class App2Controller {
    
    @RequestMapping("/t2")
    public String t1() {
        return "成功访问到资源服务B的资源";
    }

}

那么到这里就搭建好了4个微服务。

3.4、配置Nginx代理

因为要通过Nginx来访问,所以配置一下代理设置:
在这里插入图片描述
将www.ycz.com/api开头的请求全部转发到网关代理服务,由网关处理请求,决定请求是转发到指定微服务还是直接拦截。配制完毕,启动Nginx。

3.5、测试

启动eureka-server、zuul-demo、app1、app2这四个工程。

先看eureka服务中心,访问20000端口:
在这里插入图片描述
其他3个微服务成功注册到了eureka服务中心。

然后访问:http://www.ycz.com/api/app1/t1
在这里插入图片描述
打开浏览器的调试工具查看请求的header:
在这里插入图片描述
这个300状态码是我在zuul过滤器里面定义的。
在这里插入图片描述
这个请求的header里面并没有带Token令牌,所以这个请求被过滤器过滤掉了,Zuul网关不会将请求往下发,这个请求在Zuul这里就死掉了。
在这里插入图片描述
说明Zuul网关服务的过滤器的确起了作用。

现在我关掉浏览器,用postman测试。
在这里插入图片描述
header现在是完全空的,发送请求,响应如下:
在这里插入图片描述
没带Token,请求被网关过滤掉了。

然后在header里面带上Token,但value值配成错误的,再发请求:
在这里插入图片描述
Token值不匹配,请求被网关过滤掉了。

现在换成正确的Token值,再发请求:
在这里插入图片描述
网关验证通过,将请求路由给了app1服务,成功获取到了资源。

再请求其他服务的资源:
在这里插入图片描述
网关验证通过,将请求路由给了app2服务,成功获取到了资源。

4、总结

通过以上的例子可以看到,网关可以起到验证权限的作用,常用的是Zuul网关,如果资源服务接入了Security,那么可以配一个Zuul网关服务,来对所有的请求进行过滤,验证是否有权限访问资源服务的资源,比如是否带JWT令牌,JWT令牌是否正确,令牌是否过期等,这些都可以在网关服务的过滤器中进行验证,验证通过后,网关服务将请求路由给指定微服务,这时直接获取资源就行了,不用再进行验证。使用服务网关,可以保证后端服务的安全,也可以缓解后端服务的资源压力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值