微服务避免直接暴露地址,需要一个统一入口进行隔离,增强服务调用的安全性。

Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。

Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式。

Spring Cloud Gateway是替代Netflix Zuul(Zuul处于维护状态,不再进行新功能的开发)的一套解决方案,spring官方推荐我们,如果要用到网关的话使用Gateway代替Zuul.

Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 

Spring Cloud Gateway是加在整个微服务最前沿的防火墙(过滤)和代理器(路由),隐藏微服务结点IP端口信息,从而加强安全保护

Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka服务注册中心,根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由

Gateway加入后的架构

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用一切对服务的请求都可经过网关,然后再由网关来实现 鉴权、动态路由等等操作。Gateway就是我们服务的统一入口

核心概念

1、路由(routes 路由信息的组成:由一个ID(可以随意写)、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为真,说明请求URL和配置路由匹配。

2、断言(Predicate) Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于Http Request中的任何信息比如请求头和参数。

3、过滤器(Filter) 一个标准的Spring WebFilter。 Spring Cloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter(局部过滤器)和Global Filter(全局过滤器)。过滤器Filter将会对请求和响应进行修改处理

路由、断言、过滤器如下:

Spring
cloud:
 gateway:
  routes:  #路由
    # 路由id,可以随意写
   - id: user-service-route
     # 代理的服务地址;lb表示从eureka中获取具体服务
    uri: lb://user-service
     # 路由断言,可以配置映射路径
    predicates: #断言
     - Path=/**
    filters: #当前路由下的局部过滤器
      # 添加请求路径的前缀
     - PrefixPath=/user

网关的核心功能是:路由和过滤

路由routes

快速入门

需求:PC端通过网关系统将包含有/user的请求路由到http://127.0.0.1:9091/user/用户id

1、新建maven工程

2、添加依赖:spring-cloud-starter-gateway、spring-cloud-starter-netflix-eureka-client

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

3、编写启动类。注意添加注解@EnableDiscoveryClient

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

4、编写配置文件

server:
  port: 10010
spring:
  application:
    name: api-gateway
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
  instance:
    prefer-ip-address: true

5、编写路由规则,需要用网关来代理 user-service 服务,先看一下控制面板中的服务状态:

修改application.yml 文件为:

server:
  port: 10010
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        # 路由id,可以随意写
        - id: user-service-route
          # 代理的服务地址
          uri: http://127.0.0.1:9091
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/user/**
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
  instance:
    prefer-ip-address: true

id前的-表示多个的意思。path=/user/**表示路径中包含user的话,全部转到9091这台机器

将符合 Path 规则的一切请求,都代理到 uri 参数指定的地址。本例中,我们将路径中包含有 /user/** 开头的请求,代理到http://127.0.0.1:9091

注意Zuul网关的配置与Gateway不一样:

zuul:
  routes:
    manage-course: # 路由名称,名称任意,保持所有路由名称唯一
      path: /course/**
      serviceId: xc-service-manage-course #微服务名称,网关会从eureka中获取该服务名称下的服务实例的地址
      # 例子:将请求转发到http://localhost:31200/course
      #url: http://www.baidu.com #也可指定url,此url也可以是外网地址\
      strip-prefix: false #true:代理转发时去掉/course/**的前缀,false:代理转发时不去掉/course/**的前缀
      sensitiveHeaders:  #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取消默认的黑名单,如果设置了具体的头信息则不会传到下游服务
      #   ignoredHeaders: 默认为空表示不过虑任何头

6、启动测试

访问的路径中需要加上配置规则的映射路径,我们访问网关:http://localhost:10010/user/8,如果成功的话,会转到http://127.0.0.1:9091/user/8,从而实现从网关路由到指定的服务。

面向(多个实例的)服务的路由

在刚才的路由规则中,把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然不合理,可以通过配置动态路由解决。应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由

修改映射配置,通过服务名称获取

# 代理的服务地址;lb表示从eureka中获取具体服务。LoadBalance的缩写

lb://user-service

路由配置中uri所用的协议为lb时(以uri: lb://user-service为例),gateway将使用 LoadBalancerClient把user-service通过eureka解析为实际的主机和端口,并进行ribbon负载均衡

再次启动 heima-gateway ,这次gateway进行代理时,会利用Ribbon进行负载均衡访问:http://localhost:10010/user/8,控制台打印如下内容:

路由的过滤器添加前缀(添加前缀过滤器PrefixPath)PrefixPath GatewayFilter Factory

当客户端的请求地址和微服务的服务地址不一致的时候,可以通过配置过滤器实现前缀的添加与去除。

可以对请求到网关服务的地址添加或去除前缀。有些时候客户端的请求地址与微服务中的服务地址路径并不一定是一样的,那么可以在网关路由请求地址的时候,可以通过配置过滤器来实现对路径的调整。

在gateway中可以通过配置路由的过滤器PrefixPath,实现映射路径中地址的添加;修改application.yml 文件:

spring:
  cloud:
    gateway:
      routes:
        # 路由id,可以随意写
        - id: user-service-route
          # 代理的服务地址
          uri: lb://user-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/**
          filters:
            - PrefixPath=/user

注意:PrefixPath中两个P均为大写

通过 PrefixPath=/xxx 来指定了路由要添加的前缀。path=/**表示任意路径都可以执行路由。注意这里不能写成/user/**,否则浏览器通过地址http://localhost:10010/8访问网关服务时,无法进入这个路由。

也就是:(访问网关,通过网关将客户端发送的请求转发(路由)到对应的微服务

PrefixPath=/user http://localhost:10010/8 --》http://localhost:9091/user/8(提供服务的地址),即浏览器通过地址http://localhost:10010/8访问网关服务时,添加前缀/user,变成http://localhost:10010/user/8,然后将符合Path规则(/user/**)的一切请求,都代理到uri参数指定的地址,即http://localhost:9091。

如果前缀有多个,则添加多个就可以了。PrefixPath=/user/abc http://localhost:10010/8 --》http://localhost:9091/user/abc/8

路由的过滤器去除前缀(去除前缀过滤器StripPrefix)StripPrefix GatewayFilter Factory

在gateway中可以通过配置路由的过滤器StripPrefix,实现映射路径中地址的去除;修改application.yml 文件:

spring:
  cloud:
    gateway:
      routes:
        # 路由id,可以随意写
        - id: user-service-route
          # 代理的服务地址
          uri: lb://user-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/api/user/**
          filters:
            # 1表示过滤1个路径  2表示过滤两个路径,依次类推
            - StripPrefix=1

断言中path会多一个路径,当路径中有/api/user时,则会执行这个路由。即一个请求到网关服务,先根据断言中path来判断这个地址能不能进入这个路由,如果可以,则再经过过滤器,最后代理到uri指定的地址。

通过 StripPrefix=1 来指定了路由要去掉的前缀个数。如:路径 /api/user/1 将会被代理到 /user/1 。

也就是:

StripPrefix=1 http://localhost:10010/api/user/8 --》http://localhost:9091/user/8(提供服务的地址)

StripPrefix=2 http://localhost:10010/api/user/8 --》http://localhost:9091/

过滤

过滤器类型:

Gateway实现方式上,有两种过滤器;

1. 局部过滤器:通过 spring.cloud.gateway.routes.filters 配置在具体路由下,只作用在当前路由;自带的过滤器都可以配置或者自定义按照自带过滤器的方式。如果配置spring.cloud.gateway.default-filters 上会对所有路由生效也算是全局的过滤器;但是这些过滤器(局部过滤器和default-filters)的实现上都是要实现GatewayFilterFactory接口

2. 全局过滤器:不需要在配置文件中配置,作用在所有的路由上;实现 GlobalFilter 接口即可。

Gateway作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作往往是通过网关提供的过滤器来实现的。前面的 路由前缀 章节中的功能也是使用过滤器实现的。

Gateway自带过滤器有几十个,常见自带过滤器有:

局部过滤器:

局部过滤器都实现实现GatewayFilterFactory接口,该接口有一个抽象类AbstractGatewayFilterFactory,故局部过滤器可以继承该抽象类。所有局部过滤器如下:

1、局部过滤器----(也算全局)默认过滤器(default-filters)

默认过滤器对所有路由都生效,也算是全局过滤器,

这些自带的过滤器可以和使用 路由前缀 章节中的用法类似,也可以将这些过滤器配置成不只是针对某个路由;而是可以对所有路由生效,也就是配置默认过滤器:

If you would like to add a filter and apply it to all routes you can use spring.cloud.gateway.default-filters. This property takes a list of filters

spring:
  cloud:
    gateway:
      default-filters:
      - AddResponseHeader=X-Response-Default-Foo, Default-Bar
      - PrefixPath=/httpbin

本项目实例如下:

spring:
  cloud:
    gateway:
      routes:
        # 路由id,可以随意写
        - id: user-service-route
          # 代理的服务地址
          uri: lb://user-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/api/user/**
          filters:
            # 1表示过滤1个路径  2表示过滤两个路径,依次类推
            - StripPrefix=1
      default-filters:
        # 添加响应头过滤器,对输出的响应设置其头部属性名称为X-Response-Default-MyName,值为zwh;
        #如果有多个参数多则重写一行设置不同的参数
        - AddResponseHeader=X-Response-Default-MyName, zwh
        - AddResponseHeader=X-Response-Default-Mywf, xpp

上述配置后,再访问 http://localhost:10010/api/user/8 的话;那么可以从其响应中查看到如下信息:

2、局部过滤器(只作用在当前路由上)

1)、AddRequestHeader GatewayFilter Factory

The AddRequestHeader GatewayFilter Factory takes a name and value parameter.

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddRequestHeader=X-Request-Foo, Bar

This will add X-Request-Foo:Bar header to the downstream request’s headers for all matching requests.

2)、AddRequestParameter GatewayFilter Factory

The AddRequestParameter GatewayFilter Factory takes a name and value parameter.

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: http://example.org
        filters:
        - AddRequestParameter=foo, bar

This will add foo=bar to the downstream request’s query string for all matching requests.

3)、AddResponseHeader GatewayFilter Factory

The AddResponseHeader GatewayFilter Factory takes a name and value parameter.

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddResponseHeader=X-Response-Foo, Bar

This will add X-Response-Foo:Bar header to the downstream response’s headers for all matching requests.

4)、RemoveRequestHeader GatewayFilter Factory

The RemoveRequestHeader GatewayFilter Factory takes a name parameter. It is the name of the header to be removed.

spring:
  cloud:
    gateway:
      routes:
      - id: removerequestheader_route
        uri: http://example.org
        filters:
        - RemoveRequestHeader=X-Request-Foo

This will remove the X-Request-Foo header before it is sent downstream.

5)、RemoveResponseHeader GatewayFilter Factory

The RemoveResponseHeader GatewayFilter Factory takes a name parameter. It is the name of the header to be removed.

spring:
  cloud:
    gateway:
      routes:
      - id: removeresponseheader_route
        uri: http://example.org
        filters:
        - RemoveResponseHeader=X-Response-Foo

This will remove the X-Response-Foo header from the response before it is returned to the gateway client.

To remove any kind of sensitive header you should configure this filter for any routes that you may want to do so. In addition you can configure this filter once using spring.cloud.gateway.default-filters and have it applied to all routes.

6)、RequestSize GatewayFilter Factory

The RequestSize GatewayFilter Factory can restrict a request from reaching the downstream service , when the request size is greater than the permissible limit. The filter takes RequestSize as parameter which is the permissible size limit of the request defined in bytes.

spring:
  cloud:
    gateway:
      routes:
      - id: request_size_route
      uri: http://localhost:8080/upload
      predicates:
      - Path=/upload
      filters:
      - name: RequestSize
        args:
          maxSize: 5000000

The RequestSize GatewayFilter Factory set the response status as 413 Payload Too Large with a additional header errorMessage when the Request is rejected due to size. Following is an example of such an errorMessage .

errorMessage : Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB

The default Request size will be set to 5 MB if not provided as filter argument in route definition.

GatewayFilter的执行生命周期

Spring Cloud Gateway 的 Filter 的生命周期也类似Spring MVC的拦截器有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用

这里的 pre 和 post 可以通过过滤器的 GatewayFilterChain 执行filter方法前后来实现。

使用场景

常见的应用场景如下:

1、请求鉴权:一般 GatewayFilterChain 执行filter方法前,如果发现没有访问权限,直接就返回空。(执行服务之前进行鉴权)

2、异常处理:一般 GatewayFilterChain 执行filter方法后,记录异常并返回。(执行服务之后)

3、服务调用时长统计: GatewayFilterChain 执行filter方法前后根据时间统计。

Gateway自定义过滤器

自定义局部过滤器

在application.yml中对某个路由配置过滤器,该过滤器可以在控制台输出配置文件中指定名称的请求参数的值。

需求:在过滤器(MyParamGatewayFilterFactory,注意局部过滤器以GatewayFilterFactory结尾,在配置文件中配置MyParam当做自定义过滤器就可以了)中将http://localhost:10010/api/user/8?name=zwh中的参数name的值获取到并输出到控制台,并且参数名是可变的,也就是不一定每次都是name,可以通过配置过滤器的时候做到配置参数名

实现步骤:

1、配置过滤器。2、编写过滤器。3、测试

1、在heima-gateway工程编写过滤器工厂类MyParamGatewayFilterFactory。继承AbstractGatewayFilter时,要给AbstractGatewayFilter添加一个泛型,泛型就是一个配置类,这个配置就是要动态接收name这个名称。由于没有写过过滤器,可以参考RemoveResponseHeaderGatewayFilterFactroy.先找到这个类RemoveResponseHeaderGatewayFilterFactory,

public class RemoveResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<NameConfig> {
public RemoveResponseHeaderGatewayFilterFactory() {
super(NameConfig.class);
}

public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}

public GatewayFilter apply(NameConfig config) {
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
exchange.getResponse().getHeaders().remove(config.getName());
}));
};
}
}

其中NameConfig类在AbstractGatewayFilterFactory<NameConfig>中定义

public abstract class AbstractGatewayFilterFactory<C> extends AbstractConfigurable<C> implements GatewayFilterFactory<C> {
    public AbstractGatewayFilterFactory() {
        super(Object.class);
    }

    public AbstractGatewayFilterFactory(Class<C> configClass) {
        super(configClass);
    }

    public static class NameConfig {
        private String name;

        public NameConfig() {
        }

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

这里,我们在MyParamGatewayFilterFactory类中定义一个内部配置类就可以了。

@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {

public MyParamGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("param");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (request.getQueryParams().containsKey(config.param)) {
request.getQueryParams().get(config.param) //get方法获取的是一个List,而且List中只有一个值
.forEach(value ->
System.out.printf("----------局部过滤器-----%s = %s-----", config.param, value));
}
return chain.filter(exchange);
};
}
public static class Config {
// 对应在配置过滤器的时候指定的参数名,如name
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}

2、修改配置文件

spring:
  cloud:
    gateway:
      routes:
        # 路由id,可以随意写
        - id: user-service-route
          # 代理的服务地址
          uri: lb://user-service
          # 路由断言,可以配置映射路径
          predicates:
            - Path=/api/user/**
          filters:
            # 1表示过滤1个路径  2表示过滤两个路径,依次类推
            - StripPrefix=1
            #自定义过滤器
            - MyParam=name

注意:自定义过滤器的命名应该为:***GatewayFilterFactory

测试访问:http://localhost:10010/api/user/8?name=zwh检查后台是否输出name和zwh;

但是若访问http://localhost:10010/api/user/8?name2=itcast 则是不会输出的。

Gateway自定义全局过滤器

定义一个全局过滤器检查请求中是否携带有token参数

需求:模拟一个登录的校验,编写全局过滤器,在过滤器中检查请求地址是否携带token参数。如果token参数的值存在,则放行,若果不存在或为空,则设置返回的状态码未授权也不再执行下去。

基本逻辑:如果请求中有token参数(注意token只是url中的一个参数,并不是真正意义上的token),则认为请求有效,放行。

1、在zwh-gateway工程编写全局过滤器类MyGlobalFilter,实现GlobalFilter接口,如果想让过滤器有先后顺序的话,再实现Ordered.

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //getFirst方法直接获取值,而get方法获取的是List集合
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
       // setComplete()方法表示不再执行后面的过滤请求 return exchange.getResponse().setComplete(); }
return chain.filter(exchange); // 如果是正常情况下,则继续执行 } @Override public int getOrder() { //值越小越先执行 return 1; } }

访问 http://localhost:10010/api/user/8,返回没有权限。401就表示Unauthorized

访问 http://localhost:10010/api/user/8?token=abc,能够进行访问.

Gateway集成负载均衡和熔断

Gateway中默认就已经集成了Ribbon负载均衡和Hystrix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。如果不修改默认值,那么所有的都是走默认值。因此建议手动进行配置:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000
ribbon:
  ConnectTimeout: 1000 
  ReadTimeout: 2000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0

Gateway的高可用

启动多个Gateway服务,自动注册到Eureka,形成集群。这样的话,网关服务器更加可靠一些。

Ribbon与Nginx的区别

如果是服务内部访问,访问Gateway,自动负载均衡(gateway集成Ribbon负载均衡),没问题。但是,Gateway更多是外部访问,PC端、移动端等。它们无法通过Eureka进行负载均衡,那么该怎么办?此时,可以使用其它的服务网关,来对Gateway进行代理。比如:Nginx。即内部服务访问时,Gateway可以进行负载均衡。PC端、移动端通过Gateway调用微服务时使用Nginx进行负载均衡微服务使用Feign调用微服务时使用Ribbon进行负载均衡

Gateway与Feign的区别

Gateway 作为整个应用的流量入口,接收所有的请求,如PC、移动端等,并且将不同的请求转发至不同的处理微服务模块,其作用可视为nginx;大部分情况下用作权限鉴定、服务端流量控制。Gateway一般直接给PC、移动端等终端请求调用微服务,而Feign是微服务调用微服务

Feign 则是将当前微服务的部分服务接口暴露出来,并且主要用于各个微服务之间的服务调用,即Feign用于微服务之间的内部调用。服务之间的调用,由于自己的服务不会威胁到自己的服务,所以可以不经过Gateway。

Gateway跨域配置

一般网关都是所有微服务的统一入口,必然在被调用的时候会出现跨域问题。

跨域:在js请求访问中,如果访问的地址与当前服务器的域名、ip或者端口号不一致则称为跨域请求。只有在前端js发送ajax请求的时候才会出现跨域,如果是java后台端发送请求到其他服务器获取数据是不会出现跨域请求的,即服务端不存在跨域问题。若不解决则不能获取到对应地址的返回结果。

如:从在http://localhost:9090中的js访问 http://localhost:9000的数据,因为端口不同,所以也是跨域请求。

在访问Spring Cloud Gateway网关服务器的时候,出现跨域问题的话;可以在网关服务器中通过配置解决,允许哪些服务是可以跨域请求的;具体配置如下:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            #allowedOrigins: * # 这种写法或者下面的都可以,*表示全部
            allowedOrigins:
              - "http://docs.spring.io"
            allowedMethods:
              - GET

上述配置表示:可以允许来自 http://docs.spring.io 的get请求方式获取服务数据。allowedOrigins 指定允许访问的服务器地址,如:http://localhost:10000 也是可以的。'[/**]' 表示对所有访问到网关服务器的请求地址

使用浏览器http://localhost:10010/api/user/8?token=abc&name=zwh访问http://localhost:10010没有出现跨域,故可以访问到数据,且全局过滤器和局部过滤器均生效。

 

posted on 2021-03-04 11:54  周文豪  阅读(7581)  评论(0编辑  收藏  举报