SpringCloud微服务网关技术——Gateway网关的使用

一、介绍

实现微服务网关的技术有很多。

  1. nginx
    Nginx(enginex)是一个高性能的HTTP和反向代理web服务器,同时也提供 了IMAP/POP3/SMTP服务
  2. zuul ,
    Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
  3. spring-cloud-gateway,
    Gateway是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。

我们使用gateway这个网关技术,无缝衔接到基于spring cloud的微服务开发中来。

Gateway官网:https://spring.io/projects/spring-cloud-gateway

  1. Route(路由):
    这是网关的基本构建块。它由一个ID、一个URI、一组断言和一组过滤器定义。如果断言为真,则路由匹配。
  2. Predicate(断言):
    输入类型是一个 Serverwebexchange。我们可以使用它来匹配来自HTP请求的任何内容,例如 headers或参数。
  3. filter( 过滤器):
    Gateway中的 Filter分为两种类型的 Filter,分别是 GatewayFilterGlobal Filter。过滤器 Filter将会对请求和响应进行修改处理。

二、Gateway依赖

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

三、 Gateway网关的yml配置

自动转发配置

      discovery:
        locator:
          #开启从注册中心动态创建路由的功能,网关自动映射处理逻辑:http://gatewayIP:gatewayPort/微服务名称/微服务请求地址
          enabled: false # 把网关请求自动转发到微服务请求地址: http://微服务名称/微服务请求地址
          lower-case-service-id: true #开启服务名称小写装换,Eureka 对服务名称默认大写管理
          # ========以上配置就已经实现请求转发的功能了============
          # 如 http://localhost:9003/springcloud-alibaba-account/account/findAll 自动转发到 http://springcloud-alibaba-account/account/findAll
          # 商业开发中,enable 一般不设置,使用默认值false ,避免不必要的自动转发规则

跨域配置

      globalcors: # 跨域配置
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowCredentials: true
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION

全局http超时配置

      httpclient: # 配置全局 http 超时
        connect-timeout: 1000 #必须以毫秒为单位指定
        response-timeout: 5s  #必须指定为 java.time.Duration

route (路由)配置

predicates (断言/谓词)配置

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

PathRoutePredicateFactory 继承自AbstractRoutePredicateFactory
在这里插入图片描述
可以看到AbstractRoutePredicateFactory有许多的实现类,这些类的前缀就是我们谓词的参数,比如:Path、Header、Before、Query、Host、Method、Weight、Cookie、After、Between、Read等。

Path

        - id: account # 路由定义的命名 唯一即可
          uri: http://localhost:9000
          #uri: ld://springcloud-alibaba-account/ # 当前路由定义对应的转发地址 lb:服务名称 使用LoadBalancerClient 实现负载均衡
          #配置谓词集合 RoutePredicateFactory
          predicates:
            # 谓词名称是有套路的,是GatewayPredicate接口实现类的命名前缀 XxxRoutePredicateFactory
            - Path=/account/** #定义一个谓词 格式:谓词名字=参数 或者 name:名字 args:参数 
            # http://localhost:9004/account/account/findAll 转发到 http://localhost:9000/account/findAll
          # 配置过滤集合
          filters:
            - StripPrefix=1 # 将请求中的第一个路径去掉 请求路径以/区分,一个/表示一个路径,如:/api/good  会变成/good

        - id: baidu
          uri: https://www.baidu.com
          predicates:
            # http://localhost:9004/bd/ 转发至  https://www.baidu.com
            # 断言匹配到Path    转发 https://www.baidu.com/bd   去掉/bd路径
            - Path=/bd/**
          filters:
            - StripPrefix=1
            

比如 http://localhost:9004/account/account/findAll 就会被Path断言匹配到,而转发到 http://localhost:9000/account/account/findAll,经过StripPrefix会去除一个路径,最终变为:http://localhost:9000/account/findAll
在这里插入图片描述
在这里插入图片描述
http://localhost:9004/bd/ 可以断言匹配转发到百度,而http://localhost:9004/bd/a 则转发失败,原因是断言后的转发路径是https://www.baidu.com/a
在这里插入图片描述
在这里插入图片描述

Query

        - id: queryuri
          uri: https://www.qq.com/
          predicates:
            # http://localhost:9004/?uri=qq   转发至   https://www.qq.com
            # http://localhost:9004/?uri=qq&url=baidu   也可以转发到  https://www.qq.com
            # http://localhost:9004/?url=baidu&uri=qq   也可以转发到  https://www.qq.com
            # 由此可见: 只要路径中有uri=qq 就可以匹配转发  ,且断言匹配是由上到下的
            # uri中匹配qq
            - Query=uri,qq
          metadata: # 配置每条路由超时
            response-timeout: 200 # 必须以毫秒为单位指定。
            connect-timeout: 200  # 必须以毫秒为单位指定。
    
        - id: queryurl
          uri: https://www.baidu.com
          predicates:
            # http://localhost:9004/?url=baidu 转发至  https://www.baidu.com 
            # url 中匹配 baidu 
            - Query=url,baidu
                            
        - id: queayname
          uri: https://www.baidu.com
          predicates:
            # 参数name的值包含zs则会匹配成功
            - Query=name,zs.*

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Host

Header

        - id: header
          uri: http://localhost:9000
          predicates:
            # http://localhost:9004/header/account/findAll 转发至 http://localhost:9000/account/findAll
            # 路径匹配 header ,请求头中必须myheader:zhangsan  并去除 一个路径 header 才能成功转发
            - Path=/header/**
            - name: Header
              args:
                # key: value
                header: myheader
                regexp: zhangsan.*
          filters:
            - StripPrefix=1

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

在这里插入图片描述
如果没有断言的请求头信息,则不会被转发:
在这里插入图片描述

Method

        - id: queayname
          uri: https://www.baidu.com
          predicates:
            # http://localhost:9004/?url=baidu 转发至  https://www.baidu.com
            - Query=name,zs.*
            - Method=GET

使用Method属性指定方法的请求格式,如以上配置只允许GET请求:
在这里插入图片描述
在这里插入图片描述
如果修改为:- Method=GET,POST,则允许GET和POST请求:
在这里插入图片描述

Weight

负载均衡中的权重,同一个组中URI进行负载均衡 语法: weight=组名,负载均衡权重,两个的路由的Path是一样的, 但是组名都是 orderGroup 其中80%被转发到order-one,20%被转发到 order-two。
一般用于多版本发布的时候。

        - id: weight1
          uri: ld://order-one
          predicates:
              -Path=/order/**
              # 负载均衡中的权重,同一个组中URI进行负载均衡  语法: weight=组名,负载均衡权重
              -Weight=orderGroup,8
          filters:
            - StripPrefix=1
        - 
        - id: weight2
          uri: ld://order-two
          predicates:
              -Path=/order/**
              # 两个的路由的Path是一样的, 但是组名都是 orderGroup 其中80%被转发到order-one,20%被转发到 order-two
              -Weight=orderGroup,2
          filters:
            - StripPrefix=1

filters(过滤器)配置

PrefixPath

-PrefixPath添加一个路径
在这里插入图片描述

StripPrefix

请求路径以/区分 一个/代表一个路径
-StripPrefix:去掉一个路径
在这里插入图片描述

LoadBalancerClient路由过滤器(客户端负载均衡)

在这里插入图片描述

完整yml配置

4.网关限流

当我们的系统被频繁的请求的时候,就有可能将系统压垮,所以为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。

令牌桶算法

令牌桶算法是比较常见的限流算法之ー,大概描述如下
(1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理
(2)根据限流大小,设置按照一定的速率往桶里添加令牌
(3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝
(4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接除
(5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流

在这里插入图片描述

使用令牌桶进行请求次数限流

引入依赖

spring cloud gateway 默认使用redis的RateLimter限流算法来实现。所以我们要 使用首先需要引入redis的依赖

 		<!--redis  使用这个Redis依赖具有限流作用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
定义KeyResolver

在GatewayApplicatioin引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。

/**
     * 创建一个ipKeyResolver 指定用户的IP
     * 创建用户唯一标识 使用IP作为用户唯一标识,根据IP来进行限流操作
     * @return
     */
    @Bean(name="ipKeyResolver")
    public KeyResolver keyResolver(){
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                //1.获取请求request对象
                ServerHttpRequest request = exchange.getRequest();
                //2.从request中获取ip地址
                String hostString = request.getRemoteAddress().getHostString();//Ip地址
                //3.返回
                return Mono.just(hostString);
            }
        };
    }
修改application.yml中配置项

指定限制流量的配置以及REDIS的配置。
在这里插入图片描述
解释:
burstCapacity:令牌桶总容量。
replenishRate:令牌桶每秒填充平均速率。
key-resolver:用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根 据#{@beanName}从 Spring 容器中获取 Bean 对象。通过在 replenishRate 和中设置相同的值来实现稳定的速率 burstCapacity 。设置 burstCapacity 高于时,可以允许临时突发 replenishRate 。在这种情况下,需要在 突发之间允许速率限制器一段时间(根据 replenishRate ),因为2次连续突发将导致请 求被丢弃( HTTP 429 ‐ Too Many Requests )
key-resolver: “#{@userKeyResolver}” 用于通过SPEL表达式来指定使用哪一个 KeyResolver.
如上配置:
表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。 最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。

测试

启动redis
启动注册中心
启动商品微服务
启动gateway网关
打开浏览器 http://localhost:9101/goods/brand 快速刷新,当1秒内发送多次请求,就会返回429错误。

三、Gateway网关+JWT鉴权流程分析

如果不了解Jwt机制,可以点击了解一下:Jwt鉴权

服务鉴权流程:

  1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
  2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
  3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
  4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误

在这里插入图片描述

JWTUtil工具类

在这里插入图片描述

@Data
@Component
@ConfigurationProperties(prefix ="jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;

    /**
     * 生成JWT Token
     */
    public String createJWT(String id, String subject, String roles,String permission) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key)
                .claim("roles", roles)
                .claim("permission",permission);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * 解析JWT
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}

创建鉴权过滤器 验证token

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/13 10:05 上午
 * @desc :GlobalFilter是Gateway网关提供的拦截器
 */
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {
    @Autowired
    JwtUtil jwtUtil;

    /**
     * 令牌的名字
     */
    private static final String AUTHORIZE_TOKEN="Authorization";
    /**
     * 全局拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 获取用户令牌信息
        //1.请求头中   获取请求头中的第一个参数
        String token = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
        // 定义一个布尔值  为true说明令牌信息在请求头中   为false则不在
        Boolean hasToken = true;
        //2.如果请求头中没有令牌信息就从参数中获取
        if (StringUtils.isEmpty(token)){
            //表示从所有的请求参数中获取第一个
            token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            hasToken = false;
        }
        //3.Cookie中
        if (StringUtils.isEmpty(token)){
            //如果请求参数中也没有令牌信息就从Cookie中获取
            HttpCookie cookie = request.getCookies().getFirst(AUTHORIZE_TOKEN);
            if (cookie != null){
                token = cookie.getValue();
            }
        }
        // 如果没有令牌信息 则拦截
        if (StringUtils.isEmpty(token)){
            //无效   拦截
            //设置没有权限的状态码  401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //响应空数据
            return response.setComplete();
        }
        // 如果有令牌信息 则校验是否有效
        try{
            jwtUtil.parseJWT(token);
        }catch (Exception e){
            //无效   拦截
            //设置没有权限的状态码  401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //响应空数据
            return response.setComplete();

        }
        //有效  放行
        //并且将令牌信息放到请求头中
        request.mutate().header(AUTHORIZE_TOKEN,token);
        return chain.filter(exchange);
    }

    /**
     * 排序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liu_Shihao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值