• SpringCloud-GateWay


    一、背景介绍

    随着微服务架构的盛行,项目由原来的单体系统不断进行演变到现在的微服务,系统被拆分为合适粒度的多个服务,当面临多服务的调用,客户端(发送请求的一段)要如何去调用这么多的微服务呢?如果按照没有使用网关的方式去调用对于客户端是不是就需要知道每一个服务的地址信息,然后一个一个的记录去调用,这对于客户端来说是非常糟糕的;对于服务端来说也并不省事,如果需要对多个服务进行鉴权、认证等操作,单独对每个服务进行配置这样不利于实现系统的可复用性和可靠性,因此springcloud提供了GateWay


    二、过程

    GateWay是什么?

    GateWay是Spring公司提供网关,目的是为微服务架构提供简单有效的API路由管理方式以及基于Filter链的方式提供的安全、监控和限流等等功能
    官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

    网关的作用是什么?

    客户端提供统一的服务入口,当服务请求到达后端的时候先经过网关,根据网关配置的路由和断言规则进行转发到具体的某一个服务地址上,我们可以把Gateway理解为是中转站。Gateway我们可以理解为将用户请求和服务端进行了隔离,当请求来临的时候会先经过gateway,此时被拦截下来进行统一的权限判断,判断服务时候具备访问权限,如果具备权限则通过,反之则拦截。
    在这里插入图片描述
    路由:需要转发的服务地址
    断言:请求地址的时候需要满足的条件

    GateWay有什么优点?

    内置功能强度大,如:转发、监控、限流

    GateWay有什么缺点?

    不能部署在Tomcat等Servlet容器中,只能达成jar包执行
    使用时需要基于SpringBoot2.0以上的版本

    GateWay的出现是为了解决什么问题?(为什么会有网关?)

    没有使用GateWay前:

    在这里插入图片描述

    客户端需要记录每一个服务的地址;如果系统需要进行鉴权、认证,每个服务单独鉴权认证,业务复杂;存在跨域问题,解决起来复杂

    使用GateWay后:

    在这里插入图片描述

    GateWay作为所有服务统一的入口,在GateWay中我们可以统一进行鉴权、认证等等操作,当满足条件才转发到目标地址,增加了安全性

    GateWay的组成有哪些?

    • routes:路由数组

    • id:当前路由的唯一标识

    • uri:请求要转发的地址

    • order:路由的优先级,数字越小级别越高

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

      说明:按照图中配置的断言规则是:当请求url路径中是以/user-service开头就认为是符合的
      在这里插入图片描述

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


    如何使用GateWay?——实例演示

    项目版本:SpringCloud

    新建gateway服务,引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    主函数中添加@EnableDiscoveryClient注解

    在启动项中添加@EnableDiscoveryClient注解,开启nacos发现服务。所有的服务都是通过nacos注册到注册中心,而gateway服务要从nacos中去发现服务

    @SpringBootApplication
    @EnableDiscoveryClient
    public class GatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(GatewayApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    添加配置文件

    
    server:
      port: 8091
    spring:
      application:
        name: internetbar-gateway
      profiles:
        active: local
      cloud:
        nacos:
          discovery:
            server-addr: 152.136.111.77:8848
            namespace: 0759bc76-60b5-4f32-acd5-e5095cf2b93d        
        gateway:
          globalcors: # 全局的跨域处理
            add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
            corsConfigurations:
              '[/**]':
                allowedHeaders: "*"
                allowedOrigins: "*"
                allowCredentials: true
                allowedMethods: "*"
              default-filters:
                - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials Vary, RETAIN_UNIQUE
          discovery:                      #通过注册中心获取路由地址
            locator:
              enabled: true               #让gateway可以发现nacos中的微服务
          routes:                         #路由数组
            - id: user_route              #当前路由的标识,唯一标识
              uri: lb://internetbar-provider-user   #请求要转发的地址
              order: 1                    #路由的优先级,数字越小级别越高
              predicates:                 #断言(路由转发要满足什么条件)
                - Path=/user-service/**   #当请求路径满足Path指定的规则时,才进行路由转发
              filters:   #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
                - StripPrefix=1           #转发之前去掉1层路径
            - id: login_route
              uri: lb://internetbar-provider-login
              order: 1
              predicates:
                - Path=/login-service/**
              filters:
                - StripPrefix=1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    上面的实例对GateWay进行了简单的演示,接下来我们具体来看看GateWay的核心——过滤器


    Filters(过滤器)

    作用

    在请求传递的过程中允许以某种方式对请求和响应的HTTP请求做一些处理修改(在请求真正到达具体的目标服务之前,判断请求体中是否携带token、token是否过期、用户是否有权限等等)
    在这里插入图片描述

    生命周期

    Pre:前置过滤,在请求被转发到目标地址之前,通过过滤器进行身份验证等
    Post:后置过滤,请求转发到具体的某个服务之后要执行的操作,将响应从微服务端发送给客户端等

    过滤器的分类有哪些?

    • 局部过滤器(GatewayFilter)
    • 全局过滤器(GlobalFilter)

    当项目中同时配置了多种过滤器,如何选择执行那个过滤器呢?

    过滤器的执行顺序

    思想:当请求到达GateWay后,会经过默认过滤器、路由过滤器、全局过滤器,它们三个被合并到了一个过滤器集合中,根据某种规则排序后依次执行:

    • 过滤器按照order值从小到大的顺序依次执行过滤,order值越小优先级越高
    • 当过滤器的order值一样是,会按照defaultFilter>路由过滤器>GlobalFilter的顺序执行

    ①、局部过滤器

    在SpringCloud GateWay中内置了很多不同类型的网关路由过滤器,在官网中提供的有32中,当然spring非常贴心的考虑到自已自定义配置,所以开发人员也可以自定义局部过滤器
    具体的内置局部过滤器我就不一一演示了,站在巨人肩膀上,官网提供了非常详细的教程,大家可以参考使用:SpringCloud GateWay官网说明
    我们重点来说说自定义局部过滤器

    我们依旧是通过实例演示内置局部过滤器的一个使用

    场景:添加Log的过滤器配置

    package com.internet.config;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.util.Arrays;
    import java.util.List;
    
    @Component
    public class LogGatewayFilterFactory
            extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
        //构造函数
        public LogGatewayFilterFactory() {
            super(LogGatewayFilterFactory.Config.class);
        }
        
        //读取配置文件中的参数 赋值到 配置类中
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList("consoleLog", "cacheLog");
        }
        
        //过滤器逻辑@Override
        public GatewayFilter apply(LogGatewayFilterFactory.Config config) {
            return new GatewayFilter() {
                @Override
                public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                    if (config.isCacheLog()) {
    
                        System.out.println("cacheLog已经开启了	");
                    }
                    if (config.isConsoleLog()) {
                        System.out.println("consoleLog已经开启了	");
                    }
    
                	//放行
                    return chain.filter(exchange);
                }
            };
        }
    
        //配置类 接收配置参数
        @Data
        @NoArgsConstructor
        public static class Config {
            private boolean consoleLog;
            private boolean cacheLog;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    **在yml中添加日志是否开启控制**

    在这里插入图片描述

    ②、全局过滤器(GlobalFilter)

    作用:作用域所有路由,一般使用全局过滤器对权限 进行统一校验
    在SpringCloud Gateway内部也提供了一系列的内置全局过滤器
    在这里插入图片描述

    如何自定义一个全局过滤器?

    场景:登录的时候进行身份认证,认证通过后将token返回给客户端,之后的每次请求都在请求头携带token进行验证
    在这里插入图片描述

    实现流程

    客户登录的请求服务端(后端)端服务
    服务端对进行身份验证
    验证通过后将信息加密成形成token
    返回给客户端,token作为身份凭证
    之后客户端每次请求都需要携带token(我这里的实现是将token放在了请求头中)
    服务端验证是否携带token,对token进行解密之后验证token是否正确

    代码演示
    package com.internet.config;
    
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    @Component
    public class AuthGlobalFilter implements GlobalFilter , Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //获取请求体中的token
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            //验证token是否为空
            if(StringUtils.isBlank(token)){
                System.out.println("鉴权失败");
                //UNAUTHORIZED(401, "Unauthorized")
                //拦截:返回401,没有权限提示
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                //设置这个请求响应标记为已完成(说明响应已经发送并且可以被客户端接收)
                return exchange.getResponse().setComplete();
            }
    
            //调用chain.filter继续向下游执行(放行)
            return chain.filter(exchange);
        }
    
        //顺序,数值越小,优先级越高
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    如何自定义前置全局过滤器? 我们可以理解为对请求到的内容进行逻辑处理就是前置过滤器 ![在这里插入图片描述](https://img-blog.csdnimg.cn/00c51c10a1dd4c2f8ff817d223c6fc6f.png)
    @Component
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //前置过滤器
            //获取请求体中的token
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            //验证token是否为空
            if (StringUtils.isBlank(token)) {
                System.out.println("鉴权失败");
                //UNAUTHORIZED(401, "Unauthorized")
                //返回401,没有权限提示
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                //设置这个请求响应标记为已完成(说明响应已经发送并且可以被客户端接收)
                return exchange.getResponse().setComplete();
            }
    
            //调用chain.filter继续向下游执行(放行)
            return chain.filter(exchange);
        }
    
        //顺序,数值越小,优先级越高
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    如何自定义后置全局过滤器?
    当请求验证通过的时候说明可以放行,这个时候我们可以在放行中增加对应的逻辑处理
    在这里插入图片描述

    
    @Component
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //前置过滤器
            //获取请求体中的token
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            //验证token是否为空
            if (StringUtils.isBlank(token)) {
                System.out.println("鉴权失败");
                //UNAUTHORIZED(401, "Unauthorized")
                //返回401,没有权限提示
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                //设置这个请求响应标记为已完成(说明响应已经发送并且可以被客户端接收)
                return exchange.getResponse().setComplete();
            }
    
    
            //后置过滤器
            //调用chain.filter继续向下游执行(放行)
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                System.out.println("这是后置过滤器");
            }));
        }
    
        //顺序,数值越小,优先级越高
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    我们可以看一下在我们项目中网关的一个应用位置:
    在这里插入图片描述


    三、总结

    对于系统来说实现可复用是一直的追求,能重复的就抽象,减少服务和服务之间的耦合,实现可配置;暴露的内容越少安全稳定性越高,对于客户端来说只需要知道【网关IP://网关端口号:/服务接口/参数】接口,减少系统被恶意攻击的概率。

  • 相关阅读:
    Linux route命令实战:route 命令实战教程,配置静态路由,删除路由表项
    隔离和降级
    数据结构——链表
    [附源码]计算机毕业设计springboot疫情背景下社区互助服务系统
    MySQL:查询时进行时间比较
    Redis之bigkey问题解读
    RabbitMQ入门 -- 阿里云服务器安装RabbitMQ
    chmod的权限代号
    腾讯云服务器无法使用 xftp 上传文件
    【ubuntu】开机后ROS程序自启动
  • 原文地址:https://blog.csdn.net/weixin_43319713/article/details/132846728