Spring Cloud-15-Zuul网关使用及请求过滤

微服务,就是把一个大的项目拆分成多个独立的模块,然后通过服务治理让这些独立的模块配合工作,随着业务的扩展,整个系统越来越大,随之带来的也会有很多问题

比如说,我的项目中有很多个独立服务都要对外提供服务,那么对于开发和运维人员来说,这些接口应该怎么管理?
在微服务中,一个独立的系统被拆分成很多个模块,为了确保安全,我们是不是需要在每一个服务中都加上相同的鉴权代码来确保系统不被非法访问,这样的话就太繁琐了,而且不便于维护
为了解决这些问题,就需要用到API网关, 就是所有的外部访问都需要先经过网关,由网关去实现请求路由,负载均衡,权限验证等, Spring Cloud中 Spring Cloud Zuul实现了API网关功能. 下面就来看一下具体如何使用

环境搭建

新建项目

新建一个子项目,名称是api-gateway

引入依赖

引入网关和eureka的依赖即可, spring-cloud-starter-zuul这个包里面就包含了Ribbon,Hystrix,actuator等

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

开启Zuul的功能

在入口类中添加@EnableZuulProxy注解,开启Zuul的API网关服务功能.

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

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

}

配置路由规则

在application.yml中配置路由规则和项目的基本信息等,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
application:
name: api-gateway
server:
port: 2006

# 路由规则设置
zuul:
routes:
api-a:
path: /api-a/**
serviceId: feign-consumer
# 服务注册中心设置
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/

来看一下这个里的路由规则配置,path配置的值是/api-a/**,意思就是符合/api-a/**的请求,都会被转发到下面设置的serviceId上去,这里也就是会转发到feign-consumer上去,至于feign-consumer的服务地址交给服务注册中心去分析, 我们不需要关注.

以我们上面的配置为例,假如我现在请求http://localhost:2006/api-a/hello1,就相当于是请求http://localhost:2101/hello1,我这里的feign-consumer地址是http://localhost:2101/

在路由规则中配置的api-a,是路由的名字是自定义的,但是一组path和serviceId映射关系的路由名要相同.

测试

上面配置什么的都ok以后,依次启动服务注册中心,提供者,消费者和网关这些项目, 然后去访问http://localhost:2006/api-a/hello1,结果如下:
image

请求确实被转发到消费者那边了, 至此一个网关的简单搭建就ok了

请求过滤

下面再来看一下,如何通过网关实现一个简单的权限验证,这里就要用到Zuul的请求过滤的功能,类似于一个拦截器,先把请求拦截下来,然后做出相应处理,决定是否放行,下面就来看下具体如何使用

定义过滤器

首先,需要自定义一个过滤器,然后继承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
public class PermisFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

String login = request.getParameter("login");
if (login == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
ctx.addZuulResponseHeader(("content-type"), "text/html;charset=utf-8");
ctx.setResponseBody("非法访问");
}
return null;
}
}

各个方法详细说明
  • filterType(): 返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,上面代码中返回但是pre,表示是在路由之前执行过滤,其他可选值还有post,error,route和static, 也可以自定义.
  • filterOrder(): 返回过滤器的执行顺序,当过滤器有很多的时候,这个方法就有用了
  • shouldFilter(): 这个方法用来判断这个过滤器是否执行,true就是执行,在实际的使用中我们可以根据当前请求地址来决定要不要执行这个过滤器,这里为了测试,直接返回true了.
  • run():过滤器的具体逻辑,在上面的例子中,意思就是只要请求携带了login参数,就放行,否则就拦截下来,要拦截的话,首先需要设置ctx.setSendZuulResponse(false);,表示这个请求就不进行路由了,然后再设置http状态码和具体返回的body, run方法的返回值在目前的版本(Dalston.SR3)中没有任何意义,随便返回都行.

在实际开发过程中,在run方法中可能是先获取用户对象,然后做权限判断等,根据具体的业务具体实现

配置过滤器

在配置类中通过@Bean注入过滤器,代码如下:

1
2
3
4
5
6
7
8
9
@Configuration
public class Config {

@Bean
public PermisFilter permisFilter(){
return new PermisFilter();
}

}

测试

上面的内容都ok以后,就可以去启动项目进行测试了, 依次启动注册中心,服务提供者,消费者,还有网关.

启动好了之后,访问http://localhost:2006/api-a/hello1,结果如下:
image

然后再加上login参数,再次访问
image

总结

API网关作为系统的统一入口,将微服务中的内部细节都屏蔽掉了,而且能够自动的维护服务实例,实现负载均衡的路由转发,同时,它提供的过滤器为所有的微服务提供统一的权限校验机制,使得服务自身只需要关注业务逻辑即可.