Spring Cloud 7:Gateway
Zuul 网关
Zuul 是 Netfilx 开源的一个 API Gateway 服务器,本质是一个 Web Servlet 应用。其在微服务架构体系中提供动态路由、监控、弹性、安全等边缘服务。
使用 Zuul 作为网关,其主要原因有以下几点:
1、Zuul、Ribbon 以及 Consul 客户端结合使用,能够轻松实现智能路由、负载均衡功能;
2、在网关层统一对外提供 API 接口,保护了实际提供接口的微服务实现细节,同时也方便测试人员对微服务接口进行测试;
3、在网关层能够统一添加身份认证、鉴权等功能,防止对微服务 API 接口的非法调用;
4、在网关层可以方便地对访问请求进行记录,实现监控相关功能;
5、在网关层实现流量监控,在流量比较大时,方便对服务实施降级。
Zuul 工作原理
Zuul 的核心是一系列的 Filters,其作用可以类比 Servlet 框架的 Filter,或者 AOP。Zuul 中定义了四种标准过滤器类型,分别是 pre、post、routing 以及 error 过滤器。
1、pre 过滤器:在请求路由到具体微服务之前执行,其主要用于身份验证、鉴权等功能;
2、routing 过滤器:其主要功能是将请求路由到具体的微服务实例;
3、post 过滤器:在对具体微服务调用之后执行,其主要用于收集统计信息、指标以及对请求响应数据进行处理等;
4、error 过滤器:在以上三种过滤器执行出错时执行。
yang-gateway
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.M3</spring-cloud.version>
</properties>
<dependencies>
<!--Actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--Consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--Zuul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
bootstrap.yml
server: port: 1003 spring: application: name: yang-gateway cloud: consul: host: 127.0.0.1 port: 8500 discovery: register: true healthCheckPath: /server/consul/health healthCheckInterval: 10s instance-id: ${spring.application.name} zuul: routes: yang-service: path: /** serviceId: yang-diver
Application.java
@SpringBootApplication @EnableZuulProxy public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
访问路径:http://localhost:1003/user/list
此时Gateway访问到了yang-diver服务的内容了。
路由配置
传统的路由配置
在不依赖于服务发现机制的情况下,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由。
单实例配置
通过一组zuul.routes.<route>.path与zuul.routes.<route>.url参数对的方式配置。
server: port: 1003 spring: application: name: yang-gateway zuul: routes: yang-service: path: /yang-diver/** url: http://localhost:1002/
凡是路径为:http://localhost:1003/yang-diver/** 的请求,都会转发请求到http://localhost:1002/** 地址
多实例配置
通过一组zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的方式配置
zuul: routes: yang-service: path: /yang-diver/** serviceId: yang-diver ribbon: eureka: enabled: false # 没有配置服务治理(Eureka)就需要关闭,否则会找不到服务 yang-service: ribbon: # 为serviceId去指定具体的服务实例地址 listOfServers: http://localhost:1001/,http://localhost:1002/
此时,凡是路径为:http://localhost:1003/yang-diver/** 的请求,都会转发请求到http://localhost:1001/** 和http://localhost:1002/** 地址
服务路由配置
整合服务治理后,只需要提供一组zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的配置即可。
server: port: 1003 spring: application: name: yang-gateway cloud: consul: host: 127.0.0.1 port: 8500 discovery: register: true healthCheckPath: /server/consul/health healthCheckInterval: 10s instance-id: ${spring.application.name} zuul: routes: yang-service: path: /** serviceId: yang-diver
还可以通过zuul.routes.<serviceId>=<path>,直接进行路由转。,其中<serviceId>用来指定路由的具体服务名,<path>用来配置匹配的请求表达式。
zuul: routes: yang-diver: path: /** # serviceId: yang-diver
实际上,服务注册中心已经维护了serverId与实例地址的映射关系。当Gateway注册到服务注册中心后,就能从注册中心获取所有服务以及它们的实例清单。
服务网关之过滤器
Spring Cloud Zuul的过滤器的作用。
以权限控制为例。每个系统并不会将所有的微服务接口都开放出去。为了实现对客户端请求的安全校验和权限控制,有以下几点方案:
1、为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。【权限的实现方式大同小异,开发繁琐、维护困难,不推荐】
2、实现鉴权服务,直接在微服务应用中通过调用鉴权服务来实现校验。【分离不彻底】
3、通过前置的网关服务来完成这些非业务性质的校验,即通过在网关中完成校验和过滤。【推荐】
AccessFilter
``` /** * 系统访问 Filter * * @Author YangXuyue * @Date 2018/11/28 23:34 */ @Component("accessFilter") public class AccessFilter extends ZuulFilter { private static final Logger LOGGER = LoggerFactory.getLogger(AccessFilter.class); /** * 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。 * 这里定义为pre,代表会在请求被路由之前执行 * * @return * @Author YangXuyue * @Date 2018/11/28 23:39 */ @Override public String filterType() { return "pre"; } /** * 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行 * * @return * @Author YangXuyue * @Date 2018/11/28 23:39 */ @Override public int filterOrder() { return 0; } /** * 判断该过滤器是否需要被执行 * * @return * @Author YangXuyue * @Date 2018/11/28 23:39 */ @Override public boolean shouldFilter() { return true; } /** * 过滤器的具体逻辑 * 实现在请求被路由之前检查HttpServletRequest中是否有accessToken参数 * 若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。 * * @return * @throws ZuulException * @Author YangXuyue * @Date 2018/11/28 23:37 */ @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); LOGGER.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString()); Object accessToken = request.getParameter("accessToken"); if (accessToken == null) { LOGGER.warn("access token is empty"); ctx.setSendZuulResponse(false); // 未授权 ctx.setResponseStatusCode(401); return null; } LOGGER.info("access token ok"); return null; } }
此时访问:http://localhost:1003/user/list
出现401未授权的问题
如果访问:http://localhost:1003/user/list?accessToken=true
请求就能成功被转发。