spring gateway网关在微服务以及分布式里常用的作用

spring gateway网关在微服务以及分布式里常用的作用(包括yml配置说明以及各功能的原理、作用、为什么要用等详细描述)

在这里插入图片描述

主要作用有:做动态路由,认证授权(网关鉴权),令牌限流,跨域处理。

1.跨域:

概念:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
• 同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
解决跨域方法:
解决跨域 -(一)使用nginx部署为同一域
解决跨域 -(二)配置当次请求允许跨域•1、添加响应头•Access-Control-Allow-Origin:支持哪些来源的请求跨域•Access-Control-Allow-Methods:支持哪些方法跨域•Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含cookie•Access-Control-Expose-Headers:跨域请求暴露的字段•CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。•Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

2.认证授权:

API网管认证是使用预定义的一些凭证,认证某一的消费者(用户或服务)有没有权限访问当前的API。认证的目的是把没有通过认证的访问拦截掉。
为什么API网关认证很重要
API,是一种从某个端点请求数据并发送到你的中央数据库的方式。
端点可能是一下几种类型:
1.通过网络应用访问API的人类用户
2.通过物联网 API 返回数据的一组硬件或设备
3.使用内部API提交或处理数据的员工或合作商
无论哪种情况,认证都很必要。外部 API,包括面向用户的 API 和 IoT 设备的 API,最好在允许通过 API 传递数据之前就对端点认证。这可以防止错误和恶意提交数据,帮助管理传输的数据总量,并提供必要的安全层,以便您可以实施访问控制,包括在订阅过期时切断访问的选项。对于内部 API,身份验证似乎不那么重要,但它仍然是验证传输的数据是否已授权以及是否要将其传输到数据存储的重要步骤。
身份验证还使您有机会确定如何处理失败的请求。这可能意味着只需阻塞请求并返回错误代码,但在某些情况下,您可能仍然希望提供有限的访问。Kong 允许通过通过匿名认证处理这类场景。
在微服务中使用 API Gateway 认证
如果要用 Kong API 网关 认证上游微服务的访问,您必须创建一个和该微服务关联的服务。服务实体和微服务或微服务的API是一一对应的映射关系,所以实体创建后,可以把实体看作上游微服务。接着要定义路由,告诉 Kong 如何把收到的数据传给相应的服务实体。
可以把不同的路由指向同一个服务实体,比如,允许以不同的方式对不同的用户组进行身份验证。配置完选择的服务实体和路由后,就可以配置认证插件,用于根据用户凭证允许或拒绝访问。

不同等级的认证
可以使用多个插件提供不同等级的服务,增加了构建更复杂的身份验证方法的可能性。身份验证级别的一些主要示例包括:

通用认证
通用认证只是通过基础认证测试检查提供的凭证。如果通过测试,则会在报头中添加上凭证细节和消费者信息,并把请求向前传递。如果验证失败,会拦截请求。这意味着您可以通过一系列不同的方法(例如,单因素用户名和密码登录或API密钥)将访问权限限制为仅授权的个人。

匿名认证
有些情况下,您会允许没有通过凭证校验的终端用户匿名访问。比如,允许没有订阅服务的用户使用有限的服务。匿名认证通常要在插件配置中设定匿名用户ID,如果认证失败,则用这个ID代替访问。
如果没有设置匿名访问,那么认证失败的访问会显示403。

多重认证
多重认证用于对一个服务实体使用多个插件认证访问。您可以配置具体的认证方法,比如必须通过所有的认证才能访问,也就是逻辑与生命。这意味着用户必须提供更多的凭证,从而允许建立双因素(或更多)身份验证,从而显著降低未经授权的个人通过暴力攻击或泄露凭据获得访问权限的风险。
也可以选择逻辑或配置并添加匿名ID,当用户通过任一校验即可访问,而没有通过校验时,则以匿名ID访问。
当然也可以使用逻辑或且不配置匿名ID。

API gateway 之间的访问认证
最后,在某些情况下,一个API网关可能需要调用另一个API网关,例如,API网关后面的办公网络需要从另一个API网关后面的远程服务器请求数据。这类调用的身份验证可以提供一个重要的额外控制层,以确保在两个网关之间只传递有效且经过授权的请求。
这些类型的数据请求通常发生在两个网关之间,这两个网关都是作为分布式网络或广域网的一部分由您直接控制的,这样您就可以通过管理员访问这两个网关的安全配置。因此,网关到网关的访问通常是通过授权特定IP地址来实现的,IP是每个网关在其对应的安全设置中使用的,在两个网关之间创建一个IP隧道。
将另一个网关列入白名单,再加上通过安全VPN进行通信的选项,有效地阻止了对来自所有其他IP地址的数据请求的访问,从而在适当的情况下限制了对一个IP的访问,以实现最大的安全性。在一个API网关需要调用另一个API网关的场景中,这是一种允许它这样做而不向第三方未经授权的数据请求打开该网关的选项。

3.动态路由:

动态路由是指路由器能够自动地建立自己的路由表,并且能够根据实际情况的变化适时地进行调整。实现路径重写,过滤等功能

4.令牌限流:

在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。

常见的限流方式,比如Hystrix适用线程池隔离,超过线程池的负载,走熔断的逻辑。在一般应用服务器中,比如tomcat容器也是通过限制它的线程数来控制并发的;也有通过时间窗口的平均速度来控制流量。常见的限流纬度有比如通过Ip来限流、通过uri来限流、通过用户访问频次来限流。

一般限流都是在网关这一层做,比如Nginx、Openresty、kong、zuul、Spring Cloud Gateway等;也可以在应用层通过Aop这种方式去做限流。
常见的限流算法
计数器算法
计数器算法采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。具体的实现可以是这样的:对于每次服务调用,可以通过AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”

漏桶算法
漏桶算法为了消除"突刺现象",可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。

在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池(ScheduledExecutorService)来定期从队列中获取请求并执行,可以一次性获取多个并发执行。

这种算法,在使用过后也存在弊端:无法应对短时间的突发流量。

令牌桶算法
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。

Spring Cloud Gateway限流
在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现上述三种过滤器。但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用Redis和lua脚本实现了令牌桶的方式。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,具体源码不打算在这里讲述,读者可以自行查看,代码量较少,先以案例的形式来讲解如何在Spring Cloud Gateway中使用内置的限流过滤器工厂来实现限流。
首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

在配置文件中做以下的配置:

server:
  port: 8081
spring:
  cloud:
    gateway:
      routes:
      - id: limit_route
        uri: http://httpbin.org:80/get
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@hostAddrKeyResolver}'
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 3
  application:
    name: gateway-limiter
  redis:
    host: localhost
    port: 6379
    database: 0

在上面的配置文件,指定程序的端口为8081,配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:

burstCapacity,令牌桶总容量。
replenishRate,令牌桶每秒填充平均速率。
key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
KeyResolver需要实现resolve方法,比如根据Hostname进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。

public class HostAddrKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

}
@Bean
    public HostAddrKeyResolver hostAddrKeyResolver() {
        return new HostAddrKeyResolver();
    }

可以根据uri去限流,这时KeyResolver代码如下

public class UriKeyResolver  implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getURI().getPath());
    }

}
@Bean
    public UriKeyResolver uriKeyResolver() {
        return new UriKeyResolver();
    }

也可以以用户的维度去限流:

@Bean
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
    }

用jmeter进行压测,配置10thread去循环请求lcoalhost:8081,循环间隔1s。从压测的结果上看到有部分请求通过,由部分请求失败。通过redis客户端去查看redis中存在的key。如下:

可见,RequestRateLimiter是使用Redis来进行限流的,并在redis中存储了2个key。关注这两个key含义可以看lua源代码。

yml总体配置文件:

spring:
  application:
    name: sysgateway
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        - id: goods
          uri: lb://goods
          predicates:
            - Path=/goods/**
          filters:
            - StripPrefix= 1
            - name: RequestRateLimiter #请求数限流 名字不能随便写
              args:
                key-resolver: "#{@ipKeyResolver}"
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1
        - id: system
          uri: lb://system
          predicates:
            - Path=/system/**
          filters:
            - StripPrefix= 1
  # 配置Redis 127.0.0.1可以省略配置
  redis:
    host: 127.0.0.1
    port: 6379
    password: xxx
server:#该服务的端口
  port: 9101
eureka:#注册中心,不一定要这个,用于服务注册发现(主要用于其他服务的发现,例如openFeign的远程调用)
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka
  instance:
    prefer-ip-address: true
login:#网关鉴权
  filter:
    allowPaths:
      - /system/admin/login

以上是yml总体配置文件。
1.路由功能
Route(路由):路由是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。其中uri后面的lb是当多个服务的时候,启动负载均衡的功能
Predicate(谓语、断言):路由转发的判断条件,目前SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等。
Filter(过滤器):过滤器是路由转发请求时所经过的过滤逻辑,可用于修改请求、响应内容。stripPrefix转发时截取,比如请求到网关的地址是/goods/brand,网关转发到good服务的/brand地址。
2这是微服务网关跨域。在controller控制类上加个@CrossOrigin配合使用

globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE

3.网关通过过滤器做网关鉴权的功能。

login:
  filter:
    allowPaths:
      - /system/admin/login
package com.changgou.config;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
 
import java.util.List;
 
@ConfigurationProperties(prefix = "login.filter")
public class FilterProperties
{
    private List<String> allowPaths;
 
    public List<String> getAllowPaths() {
        return allowPaths;
    }
 
    public void setAllowPaths(List<String> allowPaths) {
        this.allowPaths = allowPaths;
    }
}
package com.changgou.filter;
 
import com.changgou.config.FilterProperties;
import com.changgou.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
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.util.List;
 
@Component
@EnableConfigurationProperties({FilterProperties.class})
public class loginFilter implements GlobalFilter, Ordered {
 
    @Autowired
    private FilterProperties properties;
 
    private  final static String TOKEN_NAME = "token";
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        List<String> allowPaths = properties.getAllowPaths();
        for (String allowPath : allowPaths){
            if (request.getURI().getPath().contains(allowPath)){
                return chain.filter(exchange);
            }
        }
        HttpHeaders headers = request.getHeaders();
        String token = headers.getFirst(TOKEN_NAME);
        if (StringUtils.isEmpty(token)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        try
        {
            JwtUtil.parseJWT(token);
        } catch (Exception e)
        {
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        return chain.filter(exchange);
    }
 
    @Override
    public int getOrder() {
        return 2;
    }
}

4,filters底下还可以配置网关限流的操作。是通过以上yml配置加上启动类注入一下类

配置Redis 127.0.0.1可以省略配置

redis:
  host: 127.0.0.1
  port: 6379
  password: xxxxxx
 
routes:
        - id: goods
          uri: lb://goods
          predicates:
            - Path=/goods/**
          filters:
            - StripPrefix= 1
            - name: RequestRateLimiter #请求数限流 名字不能随便写
              args:
                key-resolver: "#{@ipKeyResolver}"  #需要注入的bean类
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1

@Bean
public KeyResolver ipKeyResolver() {
return new KeyResolver() {
@Override
public Mono resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
};
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码上为赢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值