【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>
    <!--这里引入了gateway的依赖后,不能引用spring-boot-starter-web依赖。
        因为:gateway它使用的是netty服务器。
        spring-boot-starter-web里面内置了tomcat服务器.
    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>

(2)创建主启动类

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

(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/**

(4)启动gateway

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

4. gateway负载均衡转发

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

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

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

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

(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)修改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;
        }
    }
}

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

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

ribbon: 实现负载均衡

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

nacos: 注册中心

6.2 gateway中的过滤器

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

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

过滤器工厂 作用 参数
AddRequestHeader 为原始请求添加Header Header的名称及值
AddRequestParameter 为原始请求添加请求参数 参数名称及值
AddResponseHeader 为原始响应添加Header Header的名称及值
DedupeResponseHeader 剔除响应头中重复的值 需要去重的Header名称及去重策略
Hystrix 为路由引入Hystrix的断路器保护HystrixCommand的名称
FallbackHeaders 为fallbackUri的请求头中添加具体的异常信息 Header的名称
PrefixPath 为原始请求路径添加前缀 前缀路径
PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter 用于对请求限流,限流算法为令牌桶 keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo 将原始请求重定向到指定的URL http状态码及重定向的url
RemoveHopByHopHeadersFilter 为原始请求删除IETF组织规定的一系列Header 默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader 为原始请求删除某个Header Header名称
RemoveResponseHeader 为原始响应删除某个Header Header名称
RewritePath 重写原始的请求路径 原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader 重写原始响应中的某个Header Header名称,值的正则表达式,重写后的值
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;
    }
}

7. 统一跨域解决

第一种通过配置文件

spring:
 cloud:
     gateway:
       globalcors:
         cors-configurations:
           '[/**]':
             allowedOrigins: "*"
             allowedHeaders: "*"
             allowedMethods: "*"
       default-filters:
        		- DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

第二种写一个配置类

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);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_60969145/article/details/127986060