统一网关Gateway

1、为什么需要网关?

网关与各个服务之间的关系:

image-20240229110119243

网关功能:

  • 身份认证和权限校验

  • 服务路由、负载均衡

  • 请求限流

在SpringCloud中网关的实现包括两种:

  • gateway

  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloud Gateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

2、gateway快速入门

搭建网关gateway服务:

搭建网关服务的步骤:

  1. 创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<!--   网关依赖     -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--   nacos的服务发现依赖     -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  1. 编写module的启动类

    @SpringBootApplication
    public class GatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(GatewayApplication.class, args);
        }
    }
    
  2. 编写路由配置及nacos地址

    server:
      port: 10010
    spring:
      application:
        name: gateway #网关名称
      cloud:
        nacos:
          server-addr: localhost:8848 #nacos的地址
        gateway:
          routes: #网关路由设置
            - id: user-service #路由id,自定义,唯一即可
            # uri: http://127.0.0.1:8081 #路由的目标地址,http是固定地址
              uri: lb://userservice #路由的目标地址,lb就是负载均衡,后面是服务的名称
              predicates: #路由断言,也就是判断请求是否符合路由规则的条件
                - Path=/user/**  #这个是按照路由匹配,只要以/user/开头就符合要求
            - id: order-service
              uri: lb://orderservice
              predicates: #路由断言
                - Path=/order/**
    

从网关访问数据:

image-20240229113154532

image-20240229113207857

网关服务流程图:

image-20240229113341770

3、路由断言工厂Route Predicate Factory

网关路由可以配置的内容包括:

  • 路由id:路由唯一标示

  • uri:路由目的地,支持lb和http两种

  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地

  • filters:路由过滤器,处理请求或响应

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的像这样的断言工厂在SpringCloudGateway还有十几个。

Spring提供了11种基本的Predicate工厂:

名称说明示例
After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie请求必须包含某些cookie- Cookie=chocolate, ch.p
Header请求必须包含某些header- Header=X-Request-Id, \d+
Host请求必须是访问某个host(域名)- Host=.somehost.org,.anotherhost.org
Method请求方式必须是指定方式- Method=GET,POST
Path请求路径必须符合指定规则- Path=/red/{segment},/blue/**
Query请求参数必须包含指定参数- Query=name, Jack或者- Query=name
RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
Weight权重处理

可以查看官方文档来进一步学习或者使用:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

image-20240229152115249

4、过滤器工厂-路由过滤器GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

image-20240229152738630

Spring提供了31种不同的路由过滤器工厂。例如:

名称说明
AddRequestHeader给当前请求添加一个请求头
RemoveRequestHeader移除请求中的一个请求头
AddResponseHeader给响应结果中添加一个响应头
RemoveResponseHeader从响应结果中移除有一个响应头
RequestRateLimiter限制请求的流量

同样参考官方文档进行学习和参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

image-20240229153304967

示例:假如我们需要对某个服务添加请求头(路由过滤器中的一个工厂),添加配置:(最后一行,参考官方文档即可)

spring:
application:
 name: gateway #网关名称
cloud:
 nacos:
   server-addr: localhost:8848 #nacos的地址
 gateway:
   routes: #网关路由设置
        - id: user-service #路由id,自定义,唯一即可
        # uri: http://127.0.0.1:8081 #路由的目标地址,http是固定地址
          uri: lb://userservice #路由的目标地址,lb就是负载均衡,后面是服务的名称
          predicates: #路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/**  #这个是按照路由匹配,只要以/user/开头就符合要求
          filters:  
            - AddRequestHeader=hello,Mannor Redaz id the greatest man!

修改接口代码:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "hello", required = false) String hello) {
 //@RequestHeader(value = "hello", required = false) String hello
 //上述代码含义就是获取名为Hello的参数,并且这个参数不是必须的
 System.out.println("hello:" + hello);
 return userService.queryById(id);
}

测试:从网关的端口进入http://localhost:10010/user/1

image-20240229154921406

但是有一个问题就是,当我们需要向多个服务添加相同的过滤工厂的时候,每一个过滤条件都加在每一个服务配置后面就显得很不优雅,例如:image-20240229155256052

所以有个默认路由的概念:

image-20240229160110141

这样就减少了代码的复写,增强可读性。

5、全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与上一节的GatewayFilter的作用一样。

区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现

定义方式是实现GlobalFilter接口

代码实现:登录认证逻辑

package cn.pzhu.gateway;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 权限认证过滤器(全局过滤器的实现)
 * <p>
 * 需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
 * 		1.参数中是否有authorization,
 * 		2.authorization参数值是否为admin
 * 		3.如果同时满足则放行,否则拦截
 */
@Order(-1) //定义优先级,过滤器优先级越高,里面的参数值越低,可以看源码学习(也可以实现Ordered接口实现不再赘述)
@Component //将AuthorizeFilter注入到spring作为一个bean
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        //2.获取参数中的authorization参数
        String auth = params.getFirst("authorization"); //设置需要过滤的参数
        //3.判断参数值是否等于admin
        if ("admin".equals(auth)) {
            //4.是,放行
            return chain.filter(exchange);
        }
        //5. 否,拦截 (为了让用户体验到返回的结果,这里就设置响应码401,即未登录状态图码)
        //5.1 设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); //UNAUTHORIZED是401状态码的枚举类型
        //5.2 拦截请求
        return exchange.getResponse().setComplete(); //设置指示完成或错误
    }
}

测试:

image-20240229162830651

image-20240229163045659

6、各种过滤器的执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器DefaultFilterGlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器

示意图:

image-20240229164852614

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前

  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定

  • 路由过滤器defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。

  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

以上排序原因可以查看源码来推断:

**org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()**方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

image-20240229170120054

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

image-20240229170341066

7、跨域问题的解决

什么是跨域?

跨域:域名不一致就是跨域,主要包括:

  • 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com

  • 域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS

跨源资源共享CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。内容来自:mdn文档解释

前端发起一个跨域请求之后:

image-20240229172545567

网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
      add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(浏览器询问服务器是否运行跨域)
      corsConfigurations:
        '[/**]':  #拦截那些请求,/**表示拦截所以请求
          allowedOrigins: # 允许哪些网站的跨域请求
            - "http://localhost:8090"
            - "http://www.leyou.com"
          allowedMethods: # 允许的跨域ajax的请求方式
            - "GET"
            - "POST"
            - "DELETE"
            - "PUT"
            - "OPTIONS"
          allowedHeaders: "*" # 允许在请求中携带的头信息
          allowCredentials: true # 是否允许携带cookie
          maxAge: 360000 # 这次跨域检测的有效期,在这个时间内不用再次检测是否允许跨域
Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐