• 【SpringCloud】04 网关springcloud gateway


    网关springcloud gateway

    上面的架构,会存在着诸多的问题:

    1. 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性

    2. 认证复杂,每个服务都需要独立认证。

    3. 存在跨域请求,在一定场景下处理相对复杂。

    在这里插入图片描述

    网关可以做什么?

    1. 路由转发。
    2. 身份认证。
    3. 统一跨域解决。
    4. 黑白名单ip
    5. 敏感词
    6. 限流

    1. 常用的网关

    1. nginx:它可以当网关

    2. zuul:早期的微服务就是使用的该组件作为网关,但是它的底层使用的servlet。它的效率非常慢。而且它是netflix的产品。 netflix预计产品zuul2, 但是zuul2夭折。

    3. springcloud gateway:它是spring公司出品的网关。它的效率是zuul的1.6倍。

    2. springcloud gateway

    Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。

    3. 如何使用

    其实网关 它也是一个微服务,那么我们也可以创建网关微服务。

    在这里插入图片描述

    引入spring-cloud-starter-gateway

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (2)创建主启动类

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

    (3)修改配置文件

    # 配置路由
    spring:
      cloud:
        gateway:
          routes:
            - id: shop-product #路由的唯一标识。如果没有给定默认按照UUID生成
              uri: http://localhost:8001 #真实转发的地址
              predicates: # 断言 如果断言满足要求,则转发到uri指定的真实地址.
                - Path=/product/** # 如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。
    
            - id: shop-order
              uri: http://localhost:9001
              predicates:
                - Path=/order/**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (4)启动gateway

    (5)演示
    在这里插入图片描述

    4. gateway负载均衡转发

    上面配置文件有没有需要改进的?

    • 我们真实转发的地址,万一搭建是一个集群。 我们观察到gateway本身也是一个微服务,是否可以从注册中心拉取相关的微服务,然后访问该服务呢。
      在这里插入图片描述

    (1)引入nacos注册中心的依赖

    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    (2)修改配置文件
    在这里插入图片描述
    测试:
    在这里插入图片描述

    5. 简洁版

    # 配置路由
    spring:
      cloud:
        gateway:
          routes:
            - id: shop-product #路由的唯一标识。如果没有给定默认按照UUID生成
              uri: lb://shop-product #真实转发的地址 lb: ---loadbalanced
              predicates: # 断言 如果断言满足要求,则转发到uri指定的真实地址.
                - Path=/product/** # 如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。
    
            - id: shop-order
              uri: lb://shop-order
              predicates:
                - Path=/order/**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    思考: 如果这时增加新的微服务, 需要修改网关的路由配置。

    改为自动路由发现。

    (1)修改gateway的配置文件
    在这里插入图片描述
    (2)访问网关
    在这里插入图片描述

    6. gateway流程

    在这里插入图片描述

    6.1 断言的种类

    l 基于Datetime类型的断言工厂

    此类型的断言根据时间做判断,主要有三个:

    AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期

    BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期

    BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

    -After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]

    l 基于远程地址的断言工厂

    RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中

    -RemoteAddr=192.168.1.1/24

    l 基于Cookie的断言工厂

    CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求

    cookie是否具有给定名称且值与正则表达式匹配。

    -Cookie=chocolate, ch.

    l 基于Header的断言工厂

    HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否

    具有给定名称且值与正则表达式匹配。 key value

    -Header=X-Request-Id, \d+

    l 基于Host的断言工厂

    HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

    -Host=**.testhost.org

    l 基于Method请求方法的断言工厂

    MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

    -Method=GET

    l 基于Path请求路径的断言工厂

    PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

    -Path=/foo/{segment}基于Query请求参数的断言工厂

    QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具

    有给定名称且值与正则表达式匹配。

    -Query=baz, ba.

    l 基于路由权重的断言工厂

    WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发

    routes:

    -id: weight_route1 uri: host1 predicates:

    -Path=/product/**

    -Weight=group3, 1

    -id: weight_route2 uri: host2 predicates:

    -Path=/product/**

    -Weight= group3, 9

    如果上面的内置断言无法满足需求 可以自定义断言。【了解】

    案例: 年龄必须在18~65之间才能访问我指定的微服务。

    自定义断言类

    package com.aaa.predicate;
    
    import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
    import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.server.ServerWebExchange;
    
    import javax.validation.constraints.NotNull;
    import java.time.ZonedDateTime;
    import java.util.Arrays;
    import java.util.List;
    import java.util.function.Predicate;
    
    /**
     * @program: qy156-shop-parent
     * @description:
     * @author: 闫克起2
     * @create: 2022-11-21 16:27
     **/
    @Component
    public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
    
    
        public AgeRoutePredicateFactory() {
            super(AgeRoutePredicateFactory.Config.class);
        }
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList("minAge", "maxAge");
        }
        @Override
        public Predicate<ServerWebExchange> apply(Config config) {
    
            return (serverWebExchange)->{
                ServerHttpRequest request = serverWebExchange.getRequest();
                //获取传递的年龄
                String age = request.getHeaders().getFirst("age");
                if(StringUtils.hasText(age)){
                    int a = Integer.parseInt(age);
                    if(a>=config.getMinAge()&&a<=config.getMaxAge()){
                        return true;
                    }
                }
                return false;
            };
        }
    
        @Validated
        public static class Config {
            @NotNull
            private int minAge;
            @NotNull
            private int maxAge;
    
            public int getMinAge() {
                return minAge;
            }
    
            public void setMinAge(int minAge) {
                this.minAge = minAge;
            }
    
            public int getMaxAge() {
                return maxAge;
            }
    
            public void setMaxAge(int maxAge) {
                this.maxAge = maxAge;
            }
        }
    }
    
    • 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
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    在这里插入图片描述
    在这里插入图片描述

    小结:
    在这里插入图片描述
    gateway:网关,路由转发

    ribbon: 实现负载均衡

    openfeign: 完成服务之间的调用。

    nacos: 注册中心

    6.2 gateway中的过滤器

    为请求到达微服务前可以添加相应的请求设置, 响应后为响应结果添加一些设置。

    gateway内部含有很多种过滤。
    https://www.cnblogs.com/zhaoxiangjun/p/13042189.html

    过滤器工厂作用参数
    AddRequestHeader为原始请求添加HeaderHeader的名称及值
    AddRequestParameter为原始请求添加请求参数参数名称及值
    AddResponseHeader为原始响应添加HeaderHeader的名称及值
    DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
    Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名称
    FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
    PrefixPath为原始请求路径添加前缀前缀路径
    PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
    RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
    RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
    RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
    RemoveRequestHeader为原始请求删除某个HeaderHeader名称
    RemoveResponseHeader为原始响应删除某个HeaderHeader名称
    RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
    RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
    SaveSession在转发请求之前,强制执行WebSession::save操作
    secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
    SetPath修改原始的请求路径修改后的路径
    SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
    SetStatus修改原始响应的状态码HTTP 状态码,可以是数字,也可以是字符串
    StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
    Retry针对不同的响应进行重试retries、statuses、methods、series
    RequestSize设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large请求包大小,单位为字节,默认值为5M
    ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
    ModifyResponseBody修改原始响应体的内容修改后的响应体内容
    Default为所有路由添加过滤器过滤器工厂名称及值

    Tips:每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory结尾,这是Spring Cloud Gateway的一个约定,例如AddRequestHeader对应的实现类为AddRequestHeaderGatewayFilterFactory。

    举例: StripPrefix 用于截断原始请求的路径。
    在这里插入图片描述
    测试:
    在这里插入图片描述
    例子: 设置响应的状态码2500
    在这里插入图片描述
    在这里插入图片描述

    6.3 自定义全局过滤器

    例子: 认证过滤。

    内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的认证校验。

    开发中的鉴权逻辑:

    • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

    • 认证通过,将用户信息进行加密形成token[jwt],返回给客户端,作为登录凭证

    • 以后每次请求,客户端都携带认证的token [携带请求头]

    • 服务端对token进行解密,判断是否有效。

    在这里插入图片描述

    package com.aaa.filter;
    
    import com.alibaba.fastjson.JSON;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.StandardCharsets;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @program: qy156-shop-parent
     * @description:
     * @author: 闫克起2
     * @create: 2022-11-22 15:07
     **/
    @Component
    public class LoginFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            //判断请求路径是否为放行。
            String path = request.getPath().toString();
            if("/login".equals(path)){
                return chain.filter(exchange);//放行
            }
            //获取请求头的token值。
            String token = request.getHeaders().getFirst("token");
            if(StringUtils.hasText(token)){
                 //校验token是否有效
                 if("admin".equals(token)){
                     return chain.filter(exchange);//放行
                 }
            }
    
            //3.1设置状态码
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //3.2封装返回数据
            Map<String, Object> map = new HashMap<>();
            map.put("msg", "未登录");
            map.put("code", "NOTLOGING");
    
            //3.3作JSON转换
            byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);
    
            //3.4调用bufferFactory方法,生成DataBuffer对象
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
    
            //4.调用Mono中的just方法,返回要写给前端的JSON数据
            return response.writeWith(Mono.just(buffer));
        }
    
        //优先级 值越小优先级越高
        @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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    7. 统一跨域解决

    第一种通过配置文件

    spring:
     cloud:
         gateway:
           globalcors:
             cors-configurations:
               '[/**]':
                 allowedOrigins: "*"
                 allowedHeaders: "*"
                 allowedMethods: "*"
           default-filters:
            		- DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第二种写一个配置类

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.reactive.CorsWebFilter;
    import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
    import org.springframework.web.util.pattern.PathPatternParser;
    
    @Configuration
    public class CorsConfig {
        @Bean
        public CorsWebFilter corsFilter() {
            CorsConfiguration config = new CorsConfiguration();
            config.addAllowedMethod("*");
            config.addAllowedOrigin("*");
            config.addAllowedHeader("*");
    
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
            source.registerCorsConfiguration("/**", config);
    
            return new CorsWebFilter(source);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    spring中的Lifecycle
    软件测试 遇到bug却无法重现怎么办?
    【JavaWeb的从0到1的构建知识体系(四)】认识Mybatis(上)
    为什么要使用mmkv
    老卫带你学---leetcode刷题(123. 买卖股票的最佳时机 III)
    mysql 学习笔记-窗口函数之序号函数
    【Android】依赖问题
    ES6 Promise链式调用解决异步回调
    『无为则无心』Python面向对象 — 47、Python中的self详解
    IGES文件在线渲染与转换方法
  • 原文地址:https://blog.csdn.net/qq_60969145/article/details/127986060