跳至主要內容

Gateway 网关

杨轩-国实信息大约 4 分钟业务服务组件Gateway

git地址:

http://10.16.202.103:8089/component/component-ser/gosci-tech-gateway-archetype

使用archetype骨架生成时,替换自己的gav:

mvn archetype:generate -DgroupId=com.gosci.tech -DartifactId=ocean-gateway -Dversion=0.0.1-SNAPSHOT -Dpackage=com.gosci.tech.gateway -DarchetypeGroupId=com.gosci.tech -DarchetypeArtifactId=gosci-tech-gateway-archetype -DarchetypeVersion=1.0-SNAPSHOT -DinteractiveMode=false

Gateway的作用

目前,Gateway模块具有五项功能:

  • 路由转发
  • 限流,基于令牌桶算法
  • 集成权限,完成最基础的认证
  • 用户信息透传
  • 流量标记

1、路由转发

最核心的功能,将http请求和微服务的服务端分离,避免暴露真正的服务地址。常用静态路由、动态路由、自动路由三种配置方式,静态路由不支持动态添加、修改、删除路由,所以不推荐使用。

自动路由

Gateway网关通过微服务的名称,进行转发到对应的微服务:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          #开启根据服务名称自动转发
          enabled: true
          #微服务名称以小写形式呈现
          lower-case-service-id: true  

配置和使用起来非常方便,但是,个人没有采用这种方式,有两个原因:

  • 无法给单个微服务添加拦截器filter
  • 有时候微服务的服务名可能过长,前端配接口路由的时候有些太长了

所以,推荐使用下面动态路由的方式。

动态路由

动态路由是指在运行时动态添加、修改和删除路由规则,可以根据不同的条件动态地调整路由规则。实现原理是使用RouteDefinitionWriter来操作路由,通过它的 save()delete()方法可以动态的添加或删除路由规则。

具体实现可以看DynamicRouteConfig这个类,我们在代码中添加了一个nacos文件的Listener监听器,通过监听nacos上的配置文件,实现路由的动态刷新。动态路由以json的形式进行配置:

[ 
    {
		"id": "gosci-tech-basic-archetype",
		"uri": "lb://gosci-tech-basic-archetype",
		"order": 0,
		"predicates": [{
			"args": {
				"pattern": "/basic/**"
			},
			"name": "Path"
		}],
		"filters": [{
			"name": "StripPrefix",
			"args": {
				"_genkey_0": "1"
			}
		}]
	},
    {
		"id": "gosci-tech-auth-archetype",
		"uri": "lb://gosci-tech-auth-archetype",
		"order": 0,
		"predicates": [{
			"args": {
				"pattern": "/auth/**"
			},
			"name": "Path"
		}],
		"filters": [{
			"name": "StripPrefix",
			"args": {
				"_genkey_0": "1"
			}
		}]
	}	
]

2、限流

SpringCloud Gateway 中实现了一个RedisRateLimiter,底层算法基于令牌桶,使用Redis 的 lua脚本实现。配置:

spring:
  cloud:
    gateway:
      default-filters:
        - name: RequestRateLimiter
          args:
            #令牌桶每秒填充平均速率,即等价于允许用户每秒处理多少个请求平均数
            redis-rate-limiter.replenishRate: 2
            # 令牌桶容量,允许在一秒钟内完成的最大请求数
            redis-rate-limiter.burstCapacity: 10
            # 每次消费的Token数量
            redis-rate-limiter.requestedTokens: 1
            #SPEL表达式去的对应的bean
            rate-limiter: "#{@defaultRedisRateLimiter}"
            #SPEL表达式去的对应的bean
            key-resolver: "#{@apiKeyResolver}"   

对接口进行压测测试,访问20次接口:

可以看到后面的接口会被拦截,重新生成令牌后才能通过:

3、身份认证

认证

所有经过Gateway的接口,除配置在白名单外的,都需要进行基本的认证操作,认证过程由Gateway服务调用Auth服务的Feign接口完成,具体实现逻辑在AccessGatewayFilter这个类中。

实际上,认证服务是由Auth服务完成的,Gateway只负责接口调用!

实现逻辑在自定义的AccessGatewayFilter这个拦截器的filter方法中,调用Auth服务中LoginApicheckToken方法,通过传递token完成用户校验。

白名单

对于不需要认证的接口,需要在数据库中配置白名单,白名单配置在数据库的gateway_white_list这张表中。当匹配到对应的路由规则时,会直接进行放行不走权限认证:

4、用户信息透传

在通过用户认证后,Auth服务会返回给网关用户的基础信息,通过在Http请求中添加一个header的形式进行向下传递:

ServerHttpRequest newRequest = exchange.getRequest().mutate()
        .header("authUser",
                Base64Utils.encodeToString(JsonUtils.toString(authUser).getBytes(StandardCharsets.UTF_8)))
        .header(HttpHeaders.AUTHORIZATION, " ")
        .header(TAG_SOURCE, "portal")
        .build();

整体流程:

这样,在微服务中可以把用户信息反序列化后存到ThreadLocal中,随时使用。

5、流量标记

目前系统暂时没有区分门户端和运营端,流量标记可能暂时没有什么作用,但是如果之后区分两端了,那么到微服务后端的接口就需要进行区分了。

暂时规划流量标记在header中添加一个X-TAG-SOURCE的标记,值为portaladmin

注意另一个使用到的流量标记是X-TAG-FEIGN,用来表示请求是通过feign调用进行的。

gateway_white_list 建表语句

CREATE TABLE `gateway_white_list` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `path` varchar(255) NOT NULL COMMENT '白名单地址',
  `gateway_type` tinyint(2) NOT NULL COMMENT '网关类型,1:门户端网关,2:运营网关',
  `service_name` varchar(255) DEFAULT NULL COMMENT '服务名,该项白名单属于哪个服务的',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `del_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标志',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='网关白名单';