动态路由实际上就是动态配置API网关的路由参数。这样就可以实现在不重启服务的情况下,API路由规则的动态配置。
在这里选用Spring Cloud Gateway进行处理。
Spring Cloud Gateway是Spring Cloud家族中的一款API网关。因为之前 Zuul 2.x 的不断跳票,Spring Cloud 才釜底抽薪推出了自己的服务网关:Spring Cloud Gateway。Gateway 建立在 Spring Webflux上,目标是提供一个简洁、高效的API网关,同时也可以快速的拼装上Spring Cloud全家桶的API网关
Spring Cloud Gateway包含特性:
- 能够自由设置任何请求属性的路由
- 路由可以自由设置断言(Predicates)和过滤器(Filter)
- 可集成熔断器
- Spring Cloud DiscoveryClient原生支持
- 流量限速
- 路径重写
那么相比较于直接修改application的
动态路由,就是在API服务网关启动之后,路由关系可取决于外部环境的变化而变化,比如通过注册中心的不同的微服务、数据库中的映射路由关系等,动态的改变路由关系。Spring Cloud Gateway也提供了两种动态路由的方式,一种是Spring Cloud DiscoveryClient原生支持、一种是基于Actuator API。
Spring Cloud DiscoveryClient
Spring Cloud原生支持服务自动发现并且注册到路由之中,通过在application.properties中设置spring.cloud.gateway.discovery.locator.enabled=true ,同时确保 DiscoveryClient 的实体 (Netflix Eureka, Consul, 或 Zookeeper) 已经生效,即可完成服务的自动发现及注册。
创建路由关系:
{
"id": "first_route",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/first"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}]
删除:
需要使用 DELETE 请求到 /gateway/routes/{id_route_to_delete}
但上述方法自由度仍然受限。
基于服务注册发现的Spring Cloud DiscoveryClient,需要全部服务在Spring Cloud家族体系下,一旦有外部路由关系,会将思维负载化。Actuator API是一种外部API调用,虽然能够解决90%以上的问题,但是对于高度定制化的需求,频繁定制增删改查路由的API,难免会有bug,甚至修改时会造成服务的瞬时不可用。所以我们对其进行修改。采取的修改思路是动态修改,即请求进来时,以GlobalFilter的方式动态修改路由地址。
主要处理思路如下:
/**
* 设置请求头中包含网关信息的全局过滤器
* 用于标识网关转发的请求,防止微服务被直接请求
* 加密方式:将JWT进行Bcrypt加密,并将加密后的字符串放入请求头中,各微服务拦截请求头并进行校验,防止被恶意访问
*/
所以,定义了鉴权管理器,用于判断是否有资源的访问权限。
/**
* 鉴权管理器,用于判断是否有资源的访问权限
*/
@Component
class AuthorizationManager : ReactiveAuthorizationManager<AuthorizationContext> {
// @Resource
// private val redisTemplate: RedisTemplate<String, Any>? = null
对应跨域的预检请求直接放行
if (request.method == HttpMethod.OPTIONS) {
return Mono.just(AuthorizationDecision(true))
}
从Redis中获取当前路径可访问角色列表
val uri = authorizationContext.exchange.request.uri
// val obj = redisTemplate!!.opsForHash<String, String>()[RedisConstant.RESOURCE_ROLES_MAP, uri.path]
// var authorities = Convert.toList(String::class.java, obj)
var authorities = listOf<String>("TEST", "ADMIN")
authorities =
authorities.stream().map { i: String -> AuthConstant.AUTHORITY_PREFIX + i }.collect(Collectors.toList())
认证通过且角色匹配的用户可访问当前路径
return authentication
.filter(Predicate { obj: Authentication -> obj.isAuthenticated })
.flatMapIterable(Function<Authentication, Iterable<GrantedAuthority>> { obj: Authentication -> obj.authorities })
.map<String> { obj: GrantedAuthority -> obj.authority } // 判断当前用户是否有权限访问当前路径
.any(Predicate { o: String? -> authorities.contains(o) })
.map<AuthorizationDecision>(Function { granted: Boolean? -> AuthorizationDecision(granted!!) })
.defaultIfEmpty(AuthorizationDecision(false))
其他:
自定义两种返回结果:没有登录或token过期、没有权限
/**
* 网关白名单,可使得部分请求越过网关
* 通过在配置文件中配置, 如下:
* secure:
* ignore:
* urls:
* - "/api/user/login"
*/
@param publisher 用于更新路由信息
@param nacosConfigManager nacos配置管理器