网关 GateWay 的使用详解、路由、过滤器、跨域配置

一、网关的基本概念

SpringCloudGateway网关是所有微服务的统一入口。

1.1 它的主要作用是:

  • 反向代理(请求的转发)

  • 路由和负载均衡

  • 身份认证和权限控制

  • 对请求限流

1.2 相比于Zuul的优势:

SpringCloudGateway基于Spring5中提供的WebFlux,是一种响应式编程的实现,性能更加优越。

Zuul的实现方式比较老式,基于Servlet的实现,它是一种阻塞式编程,在高并发下性能性能不佳。

拓展:

其实Nginx也可以作为网关,但是要使用Nginx自主实现网关的相关功能,还需要借助lua脚本语言,学习成本是比较高的,现在一般也不会使用它来做网关,但是只按性能来讲Nginx,性能是最高的。

1.3 SpringCloudGateway架构图:

微服务只接收来自网关的请求,而其它直接访问微服务本身的请求拒绝。

这样可以极大保护微服务免受不法侵害。

同时在请求压力激增时,可以实施服务限流,保护微服务集群。

二、SpringBoot中配置GateWay

2.1 引入GateWay的Maven依赖

复制代码
<!--网关 起步依赖-->
<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>

2.2 配置application.yml文件

复制代码
server:
  port: 10086   # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848    # nacos 地址
    gateway:
      routes:         # 网关路由配置
        - id: user-service      # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081   # 路由的目标地址 (直接写死地址的方式,不推荐)
          uri: lb://userservice    # 路由的目标地址 lb是负载均衡,后面跟服务名称(推荐)
          predicates:       # 路由断言,判断请求是否符合路由规则的条件
            - Path=/user/**      # 按照路径匹配,以/user/开头的请求就符合要求
        - id: card-service
          uri: lb://cardservice
          predicates:
            - Path=/card/**

gateway配置中的注意点:

1.routes 后面的路由可以配置多个,相当于配置个数组,一个-开头的配置就是其中的一个数组元素。

2.uri为什么选择以服务名+负载均衡的方式?

主要是写死地址的话,今后如果userservice的地址变了,那么又要去修改yml配置文件。而lb://userservice可以让程序员一眼认出这是哪个微服务,以后地址变了也无需修改yml配置文件。

上述配置详解:

/user/**开头的请求,代理到lb://userservice

/card/**开头的请求,代理到lb://cardservice

lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。

  • http://127.0.0.1:10086/user/99 就算是/user/**开头的请求,不要把协议、ip和端口计算在内。

  • 有多少个需要配置的路由,都按上面的格式配置即可

三、GateWay路由配置详解

路由主要有四个配置:

  • 路由id(id)

  • 路由目标(uri)

  • 路由断言(predicates):判断路由的规则,

  • 路由过滤器(filters):对请求或响应做处理

3.1 路由id

当前路由的唯一标识。

3.2 路由目标

路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡。

一般都不会选择写死http固定地址的方式。而是选择可维护性更强的lb根据服务名负载均衡的方式。

具体优势如上所言。

3.3 路由断言

路由断言主要用来判断路由的规则。

配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理。

例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理。

像这样的断言工厂在SpringCloudGateway还有十几个:

实际使用时,根绝业务要求选择使用即可。

不过一般来讲,最常用的是使用Path这种断言工厂,仅用它就能满足常见的需求了。

关于Path断言工厂的补充:

  • Path=/card/**代表 以/card/路径开头的多级路径请求,这么写多级路径请求和一级路径请求都生效。

  • Path=/card/*代表 以/card/路径开头的一级路径请求,这么写多级路径请求将不会生效。

断言工厂官方文档:

今后如果有复杂的断言工厂配置,可以参照官网文档上的例子去实现。

3.4 路由过滤器(filters)

路由过滤器对请求或响应做处理。

客户端请求先找到路由,路由匹配时经过过滤器层层筛选,最终访问到微服务。

当然微服务的请求反悔时,也会经过过滤器的筛选,只不过我们一般只对请求过滤,而不会对响应过滤。

SpringCloudGateWay目前已经提供了34种不同的过滤器工厂。

常用的几个有:

3.4.1 请求头过滤器配置示例(局部过滤器)
复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/** 
        filters:         # 过滤器配置
        - AddRequestHeader=token, test # 添加请求头

上述过滤器的含义:

给所有进入userservice的请求添加一个请求头。

请求头的key为token,value为test。

由于当前前过滤器写在微服务的userservice路由下,因此仅仅对访问微服务userservice的请求有效。

3.4.2 默认过滤器配置示例(全局过滤器)
复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-service 
          uri: lb://userservice 
          predicates: 
          - Path=/user/**
      default-filters:      # 默认过滤器配置
        - AddRequestHeader=token, test  # 添加请求头

default-filters的配置和routes平级。

只要配置在default-filters下面的过滤器,会对routes配置的所有路由都生效。

过滤器工厂官方文档:

今后如果有复杂的断言工厂配置,可以参照官网文档上的例子去实现。

四、自定义全局路由过滤器

有时候SpringCloudGateWay提供的过滤器工厂不能满足自己的要求。

可能有时候需要在过滤时做一些其它的逻辑操作。

那么这时候可以选择使用java代码自定义全局过滤器。

代码示例:

复制代码
@Component
public class GateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1.获取请求参数 
        //1.这里的request并不是servlet中的request  
        //2.返回值是一个多键的map集合、也就是说这个map集合的键可以重复
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // 2.获取userName参数
        String userName = params.getFirst("userName");
        // 3.校验
        if ("root".equals(userName)) {
            // 放行
            return chain.filter(exchange);
        }
        // 4.拦截
        // 4.1.禁止访问,设置状态码
        exchange.getResponse().setStatusCode(500);
        // 4.2.结束处理
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

当有多个过滤器时,Order的值决定了过滤器的执行顺序。

数值越大优先级越低, 负的越多, 优先级越高。

设置Order的值有两种方式:

1. 实现Ordered接口,并且重写getOrder方法

复制代码
@Component
public class GateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    }

    @Override
    public int getOrder() {
        return -1;
    }
}

2. 使用@Order注解

复制代码
@Order(-1)
@Component
public class GateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    }
}

五、过滤路由过滤器的执行顺序

5.1 过滤器的种类

SpringCloudGateWay中,有三种过滤器:

  • 默认过滤器default-filters

  • 只对具体某个路由生效的局部过滤器filters

  • 使用java代码编写的全局过滤器GlobalFilter

5.2 过滤器的执行顺序

由上图知过滤器的执行顺序为:默认过滤器 → 当前路由过滤器 → 全局过滤器。

六、网关的跨域问题

6.1 跨域的概念和原理

跨域:请求位置和被请求位置不同源就会发生跨域。

这里的不同源包括两个点:

而浏览器又会禁止请求的发起者与服务端发生跨域AJAX请求。

如果发生了跨域请求,服务器端是能够正常响应的,但是响应的结果会被浏览器拦截。

6.2 跨域常见解决方案

使用CORS方式。

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

6.3 gateway中如何解决跨域问题

方式一:配置application.yml文件:

复制代码
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域配置
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
               # options请求 就是一种询问服务器是否浏览器可以跨域的请求
               # 如果每次跨域都有询问服务器是否浏览器可以跨域对性能也是损耗
               # 可以配置本次跨域检测的有效期maxAge
               # 在maxAge设置的时间范围内,不去询问,统统允许跨域
        corsConfigurations:
          '[/**]':
            allowedOrigins:   # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods:   # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*"  # 允许在请求中携带的头信息
            allowCredentials: true # 允许在请求中携带cookie
            maxAge: 360000    # 本次跨域检测的有效期(单位毫秒)
                  # 有效期内,跨域请求不会一直发option请求去增大服务器压力

方式二:使用编码方式定义配置类:

复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Configuration
public class CorsConfig {
    private static final String MAX_AGE = "18000L";

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            // 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域
            if (!CorsUtils.isCorsRequest(request)) {
                return chain.filter(ctx);
            }
            HttpHeaders requestHeaders = request.getHeaders();                                  // 获取请求头
            ServerHttpResponse response = ctx.getResponse();                                    // 获取响应对象
            HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();          // 获取请求方式对象
            HttpHeaders headers = response.getHeaders();                                        // 获取响应头
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());   // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins)
            headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
            if (requestMethod != null) {
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());    // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods)
            }
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");          // 允许在请求中携带cookie(相当于yml中的allowCredentials)
            headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");                // 允许在请求中携带的头信息(相当于yml中的allowedHeaders)
            headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);                           // 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge)
            if (request.getMethod() == HttpMethod.OPTIONS) {                                    // 直接给option请求反回结果
                response.setStatusCode(HttpStatus.OK);
                return Mono.empty();
            }
            return chain.filter(ctx);                                                           // 不是option请求则放行
        };
    }

}
相关推荐
Java后端的Ai之路18 小时前
【Spring全家桶】-一文弄懂Spring Cloud Gateway
java·后端·spring cloud·gateway
研究司马懿5 天前
【云原生】Gateway API介绍
云原生·gateway
研究司马懿5 天前
【云原生】Gateway API路由、重定向、修饰符等关键操作
云原生·gateway
研究司马懿5 天前
【云原生】初识Gateway API
云原生·gateway
七夜zippoe6 天前
API网关设计模式实战 Spring Cloud Gateway路由过滤限流深度解析
java·设计模式·gateway·路由·api网关
汪碧康6 天前
一文讲解kubernetes的gateway Api的功能、架构、部署、管理及使用
云原生·容器·架构·kubernetes·gateway·kubelet·xkube
大佐不会说日语~6 天前
Docker Compose 部署 Spring Boot 应用 502 Bad Gateway 问题排查与解决
spring boot·docker·gateway·maven·故障排查
Dontla8 天前
Kubernetes流量管理双雄:Ingress与Gateway API解析(Nginx与Ingress与Gateway API的关系)
nginx·kubernetes·gateway
JavaLearnerZGQ8 天前
Gateway网关将登录用户信息传递给下游微服务(完整实现方案)
微服务·架构·gateway
Ares-Wang9 天前
网络》》BGP Border Gateway Protocol,边界网关协议
网络·gateway