13

月光中的污点 | Spring Cloud 入门 之 Zuul 篇(五)

 4 years ago
source link: https://www.extlight.com/2019/09/11/Spring-Cloud-%E5%85%A5%E9%97%A8-%E4%B9%8B-Zuul-%E7%AF%87%EF%BC%88%E4%BA%94%EF%BC%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

随着业务的扩展,微服务会不对增加,相应的其对外开放的 API 接口也势必增多,这不利于前端的调用以及不同场景下数据的返回,因此,我们通常都需要设计一个 API 网关作为一个统一的 API 入口,来组合一个或多个内部 API。

二、简单介绍

2.1 API 网关使用场景

1
2
3
4
5
6
7
8
9
10
11
黑白名单: 实现通过 IP 地址控制请求的访问

日志:实现访问日志的记录,进而实现日志分析,处理性能指标等

协议适配:实现通信协议的校验、适配转换的功能

身份认证:对请求进行身份认证

计流限流:可以设计限流规则,记录访问流量

路由:将请求进行内部(服务)转发

2.2 API 网关的实现

业界常用的 API 网关有很多方式,如:Spring Cloud Zuul、 Nginx、Tyk、Kong。本篇介绍的对象正是 Spring Cloud Zuul

Zuul 是 Netflix 公司开源的一个 API 网关组件,提供了认证、鉴权、限流、动态路由、监控、弹性、安全、负载均衡、协助单点压测等边缘服务的框架。

Spring Cloud Zuul 是基于 Netflix Zuul 的微服务路由和过滤器的解决方案,也用于实现 API 网关。其中,路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入门的基础。而过滤功能是负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。

Spring Cloud Zuul 和 Eureka 进行整合时,Zuul 将自身注册到 Eureka 服务中,同时从 Eureka 中获取其他微服务信息,以便请求可以准确的通过 Zuul 转发到具体微服务上。

三、实战演练

本次测试案例基于之前发表的文章中介绍的案例进行演示,不清楚的读者请先转移至 《Spring Cloud 入门 之 Hystrix 篇(四)》 进行浏览。

当前的项目列表如下:

服务实例 端口 描述 common-api - 公用的 api,如:实体类 eureka-server 9000 注册中心(Eureka 服务端) goods-server 8081 商品服务(Eureka 客户端) goods-server-02 8082 商品服务(Eureka 客户端) goods-server-03 8083 商品服务(Eureka 客户端) order-server 8100 订单服务(Eureka 客户端)

创建一个为名 gateway-server 的 Spring Boot 项目。

3.1 添加依赖

1
2
3
4
5
6
7
8
9
10
11
<!-- eureka 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- zuul 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

3.2 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 9600

spring:
application:
name: gateway

eureka:
instance:
instance-id: gateway-9600
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址

3.3 启动 Zuul

在启动类上添加 @EnableZuulProxy 注解:

1
2
3
4
5
6
7
8
@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {

public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

启动上边的所有项目,打开 Postman 请求订单下单接口,如下图:

zuul-01.gif

图中,我们首先不经过网关直接访问 order-server 项目请求地址:http://localhost:8100/order/place

之后再修改成访问 gateway-server 项目的请求地址:http://localhost:9600/order/order/place

最终,响应结果都一样。

提示:http://localhost:9600/order/order/place 中第一个 order 表示的是注册在 Eureka 上的订单服务名称。

3.4 zuul 常用配置

修改路由:

1
2
3
4
5
6
zuul:
sensitive-headers: # 全局忽略敏感头,即允许接收 cookie 等请求头信息
routes:
extlight: # 任意名字,保证唯一即可
path: /extlight/** # 自定义,真正用到的请求地址
service-id: ORDER # 路由到的目标服务名称

将订单服务的路由名称改成 extlight。

使用 Postman 请求下单接口,运行结果:

zuul-02.gif

请求成功。

禁用路由:

1
2
3
zuul:
ignored-patterns:
- /order/order/**

http://localhost:9600/order/order/place 无法被正常路由到订单服务,响应返回 404。

路由加前缀:

1
2
zuul:
prefix: /api

所有请求中的 path 需要添加 api 前缀。如: http://localhost:9600/extlight/order/place 需要改成 http://localhost:9600/api/extlight/order/place

设置敏感头:

1
2
zuul:
sensitive-headers: # 设置全局敏感头,如果为空,表示接收所有敏感头信息
1
2
3
4
5
6
zuul:
routes:
extlight: # 任意名字,保证唯一即可
path: /extlight/** # 自定义,真正用到的请求地址
service-id: ORDER # 路由到的目标服务名称
sensitive-headers: # 针对 /extlight/ 的请求设置敏感头信息

四、Zuul 自定义过滤器

Zuul 的核心技术就是过滤器,该框架提供了 ZuulFilter 接口让开发者可以自定义过滤规则。

我们以身份检验为例,自定义 ZuulFilter 过滤器实现该功能。

4.1 创建用户服务

新建名为 user-server 的项目。

添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- common api -->
<dependency>
<groupId>com.extlight.springcloud</groupId>
<artifactId>common-api</artifactId>
<version>${parent-version}</version>
</dependency>

<!-- springmvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- eureka 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8200

spring:
application:
name: USER

eureka:
instance:
instance-id: user-api-8200
prefer-ip-address: true # 访问路径可以显示 IP
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址

登录接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
@RequestMapping("/user")
public class LoginController {

@PostMapping("/login")
public Result login(String username, String password, HttpServletResponse response) {


if ("admin".equals(username) && "admin".equals(password)) {
// 模拟生成 token,实际开发中 token 应存放在数据库或缓存中
String token = "123456";
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
cookie.setMaxAge(60 * 10);
response.addCookie(cookie);

return Result.success();
}

return Result.fail(401, "账号或密码错误");
}
}

user-server 启动类:

1
2
3
4
5
6
7
8
@EnableEurekaClient
@SpringBootApplication
public class UserServerApplication {

public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class, args);
}
}

4.2 创建 ZuulFilter 过滤器

在 gateway-server 项目中,新建一个过滤器,需要继承 ZuulFilter 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Component
public class AuthenticationFilter extends ZuulFilter {

/**
* 是否开启过滤
*/
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();

boolean flag = request.getRequestURI().contains("/login");
// 如果是登录请求不进行过滤
if (flag) {
System.out.println("========不执行 zuul 过滤方法=======");
} else {
System.out.println("========执行 zuul 过滤方法=======");
}
return !flag;
}

/**
* 过滤器执行内容
*/
@Override
public Object run() throws ZuulException {

RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getParameter("token");
// 此处模拟获取数据库或缓存中的 token
String dbToken = "123456";
// 此处简单检验 token
if (token == null || "".equals(token) || !dbToken.equals(token)) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}

return null;
}

/**
* 过滤器类型
*/
@Override
public String filterType() {
return "pre";
}

/**
* 过滤器执行顺序
*/
@Override
public int filterOrder() {
return 0;
}

}

其中,filterType 有 4 种类型:

1
2
3
4
5
6
7
pre: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

routing:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。

post:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

error:在其他阶段发生错误时执行该过滤器。

其过滤顺序如下图:

zuul-04.jpg

4.3 测试过滤器

运行所有项目,测试操作步骤如下:

1
2
3
4
5
请求用户服务的登录接口(http://localhost:9600/user/user/login),请求不执行 zuul 过滤方法,并且请求响应返回的 cookie 包含 token

请求订单服务的下单接口(http://localhost:9600/extlight/order/place),但不携带 token,请求需要执行 zuul 过滤方法,请求响应 401 权限不足

请求订单服务的下单接口(http://localhost:9600/extlight/order/place),携带之前登录接口返回的 token,请求需要执行 zuul 过滤方法,校验通过后路由到订单服务执行之后的操作

测试效果图如下:

zuul-03.gif

五、案例源码

Zuul demo 源码

六、参考资料

Announcing Zuul: Edge Service in the Cloud


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK