网关是微服务架构中非常重要的一个组件,在微服务应用中,客户端所有的请求都是先经过网关,然后再转发到具体的微服务上,客户端无需知道具体微服务的地址,知道网关的地址即可。下面对Spring Cloud GateWay网关进行简单的聊聊。
1、单体架构
在传统的springboot单体应用中,一般只有一个后端服务,如下
2、微服务架构
在springcloud微服务架构中,往往有多个微服务,这些微服务可能部署在不同的机器上,而且一个微服务可能会扩容成多个相同的微服务,组成微服务集群。
上面的情况下,会出现如下问题
痛点其实就是各个微服务都有一个入口,没有统一的入口,这时在客户端和服务器之间增加一个节点作为唯一的入口,这个就是网关。
多个微服务之间若需要进行通信,还需要用到远程调用组件了,如OpenFeign。
3、网关
应用场景,有三个节点:网关、客户端、服务端,其通信对话如下
网关:客户端你好,你可以访问我了,我可以将你想发给微服务的流量进行转发,微服务处理后再将结果饭给你;
客户端:网关你好,你没有赚差价吧?
网关:客户端你好,我可能会加些请求头,做下认证、鉴权、限流;
客户端:网关你好,微服务自己可以做吧?
网关:客户端你好,每个微服务都自己加,很麻烦了,也不统一呀;
服务端:网关你好,你会为微服务保密地址吗?
网关:服务端你好,当然会保密了,客户端只知道我网关的地址,不需要知道微服务的地址,只需要知道路由就行,剩下的交给我来转发;
4、Spring Cloud Gateway网关
gateway的工作流程如下
客户端的请求到达网关后,先经过断言Predicate判断,符合那个路由规则,转换映射后端的某个服务;然后经过过滤器链,对请求进行拦截,如添加请求头,参数校验等,最后再将请求转发到实际的后端服务;后端服务处理完成之后,将结果返回给网关,网关再次进行过滤,然后将结果返回给客户端。
断言配置的规则示例如下
spring:
cloud:
gateway:
routes:
- id: route_order # 订单微服务路由规则
uri: lb://order # 负载均衡,将请求转发到注册中心注册的order 服务
predicates: # 断言
- Path=/api/order/** # 如果前端请求路径包含 api/order,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空
- id: route_member # 会员微服务路由规则
uri: lb://member # 负载均衡,将请求转发到注册中心注册的 member 服务
predicates: # 断言
- Path=/api/member/** # 如果前端请求路径包含 api/member,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的 api 替换成空
其中uri: lb://member,标识将请求转发给member微服务,且支持负载均衡,lb是loadbalance的缩写。
5、Spring Cloud Gateway动态路由
微服务架构中,不会直接通过IP+端口的方式来访问微服务,而是通过服务名的方式来访问,这时引入了注册中心,多个微服务将自己注册到注册中心,这样注册中心就保存了微服务与IP+端口的映射信息。
动态路由就算添加一个微服务或者IP地址变了,网关都可以感知到,但是配置是不需要更新的,微服务的集群个数、IP和端口是动态可变的。
那么此时整合各个组件之后,整体的架构中请求的转发如下
客户端先将请求发送给Nginx,然后转发给网关,网关经过断言匹配到一个路由后,将请求进行过滤然后转发给知道uri,这个uri可以配置成微服务的名字,gateway网关从注册中心拉取注册表,就能知道服务名对应的IP+端口,若一个微服务部署了多台机器,则通过负载均衡进行请求的转发。
6、Spring Cloud Gateway过滤
gateway中的过滤器就是统一对请求和响应进行一些过滤操作,安装请求和响应分为两种:Pre类型和Post类型。
按作用范围也可以分为:全局过滤器和局部过滤器。
gateway网关也自带了很多过滤器,详细查看官网。
在gateway网关中做一个自定义的登录认证过滤器,如客户端登录时,将用户名和密码发送给网关,网关转发给认证服务器,如果账号密码正确,则拿到一个JWT token,然后客户端再访问应用服务时,先将请求发送给网关,网关统一做token认证,如果token符合条件,再将请求转发给应用服务,流程如下
实例
过滤器代码
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
String token = request.getHeaders().getFirst("token");
if(!antPathMatcher.match("/login", path)) {
if(StringUtils.isEmpty(token) || !"admin".equals(token)) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
}
//内部服务接口,不允许外部访问
if(antPathMatcher.match("/**/inner/**", path)) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
private Mono<Void> out(ServerHttpResponse response) {
String message = "口令失效.";
byte[] bits = message.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
未携带token
携带token