经过服务网关统一贯外系统提供REST API的过程当中,除了具有服务路由、均衡负载功能以外,它还具有了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的做用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体可以具有更高的可复用性和可测试性。html
下面咱们经过实例例子来使用一下Zuul来做为服务的路有功能。java
在使用Zuul以前,咱们先构建一个服务注册中心、以及两个简单的服务,好比:我构建了一个service-A,一个service-B。而后启动eureka-server和这两个服务。经过访问eureka-server,咱们能够看到service-A和service-B已经注册到了服务中心。git
若是您还不熟悉如何构建服务中心和注册服务,请先阅读1--SpringCloud的服务注册与发现Eurekaweb
首先建立一个简单的spring-boot项目zuul-getwayspring
pom文件以下:api
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 网关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
应用主类使用@EnableZuulProxy
注解开启Zuul安全
@EnableZuulProxy @SpringCloudApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } @Bean public AccessFilter accessFilter() { return new AccessFilter(); } }
这里用了@SpringCloudApplication
注解,以前没有提过,经过源码咱们看到,它整合了@SpringBootApplication
、@EnableDiscoveryClient
、@EnableCircuitBreaker
,主要目的仍是简化配置。架构
application.properties 配置文件app
spring.application.name=api-gateway
server.port=5555
经过服务路由的功能,咱们在对外提供服务的时候,只须要经过暴露Zuul中配置的调用地址就可让调用方统一的来访问咱们的服务,而不须要了解具体提供服务的主机信息了。ide
在Zuul中提供了两种映射方式:
zuul.routes.api-a-url.path=/api-a-url/** zuul.routes.api-a-url.url=http://localhost:2222/
该配置,定义了,全部到Zuul的中规则为:/api-a-url/**
的访问都映射到http://localhost:2222/
上,也就是说当咱们访问http://localhost:4444/api-a-url/add?a=1&b=2
的时候,Zuul会将该请求路由到:http://localhost:2222/add?a=1&b=2
上。
其中,配置属性zuul.routes.api-a-url.path中的api-a-url部分为路由的名字,能够任意定义,可是一组映射关系的path和url要相同,下面讲serviceId时候也是如此。
经过url映射的方式对于Zuul来讲,并非特别友好,Zuul须要知道咱们全部为服务的地址,才能完成全部的映射配置。而实际上,咱们在实现微服务架构时,服务名与服务实例地址的关系在eureka server中已经存在了,因此只须要将Zuul注册到eureka server上去发现其余服务,咱们就能够实现对serviceId的映射。例如,咱们能够以下配置:完整application.properties 配置文件
spring.application.name=api-gateway #启动端口 server.port=4444 #服务中心地址 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=service-A zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=service-B
针对咱们在准备工做中实现的两个微服务service-A和service-B,定义了两个路由api-a和api-b来分别映射。另外为了让Zuul能发现service-A和service-B,也加入了eureka的配置。
接下来,咱们将eureka-server、service-A、service-B以及这里用Zuul实现的服务网关启动起来,在eureka-server的控制页面中,咱们能够看到分别注册了service-A、service-B以及api-gateway
尝试经过服务网关来访问service-A和service-B,根据配置的映射关系,分别访问下面的url
http://localhost:4444/api-a/add?a=1&b=2
:经过serviceId映射访问service-A中的add服务http://localhost:4444/api-b/add?a=1&b=2
:经过serviceId映射访问service-B中的add服务http://localhost:4444/api-a-url/add?a=1&b=2
:经过url映射访问service-A中的add服务(本示例并无使用url映射,能够在配置文件里面去掉serviceId映射配置,添加上url映射)推荐使用serviceId的映射方式,除了对Zuul维护上更加友好以外,serviceId映射方式还支持了断路器,对于服务故障的状况下,能够有效的防止故障蔓延到服务网关上而影响整个系统的对外服务
在完成了服务路由以后,咱们对外开放服务还须要一些安全措施来保护客户端只能访问它应该访问到的资源。因此咱们须要利用Zuul的过滤器来实现咱们对外服务的安全控制。
在服务网关中定义过滤器只须要继承ZuulFilter
抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
好比下面的例子,定义了一个Zuul过滤器,实现了在请求被路由以前检查请求中是否有accessToken
参数,如有就进行路由,若没有就拒绝访问,返回401 Unauthorized
错误。
package com; import javax.servlet.http.HttpServletRequest; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; public class AccessFilter extends ZuulFilter{ public Object run() {//过滤器的具体逻辑 // TODO Auto-generated method stub RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object accessToken = request.getParameter("accessToken"); if(accessToken == null) { ctx.setSendZuulResponse(false);//不进行路由 ctx.setResponseStatusCode(401); return null; } return null; } public boolean shouldFilter() {//返回一个boolean类型来判断该过滤器是否要执行,因此经过此函数可实现过滤器的开关 // TODO Auto-generated method stub return true;//true 总的是执行 } @Override public int filterOrder() {//过滤的执行顺序 // TODO Auto-generated method stub return 0; } /* (non-Javadoc) * @see com.netflix.zuul.ZuulFilter#filterType() * pre:能够在请求被路由以前调用 routing:在路由请求时候被调用 post:在routing和error过滤器以后被调用 error:处理请求时发生错误时被调用 */ @Override public String filterType() {//过滤器的执行时间 // TODO Auto-generated method stub return "pre"; } }
自定义过滤器的实现,须要继承ZuulFilter
,须要重写实现下面四个方法:
filterType
:返回一个字符串表明过滤器的类型,在zuul中定义了四种不一样生命周期的过滤器类型,具体以下:
pre
:能够在请求被路由以前调用routing
:在路由请求时候被调用post
:在routing和error过滤器以后被调用error
:处理请求时发生错误时被调用filterOrder
:经过int值来定义过滤器的执行顺序shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,因此经过此函数可实现过滤器的开关。在上例中,咱们直接返回true,因此该过滤器老是生效。run
:过滤器的具体逻辑。须要注意,这里咱们经过ctx.setSendZuulResponse(false)
令zuul过滤该请求,不对其进行路由,而后经过ctx.setResponseStatusCode(401)
设置了其返回的错误码,固然咱们也能够进一步优化咱们的返回,好比,经过ctx.setResponseBody(body)
对返回body内容进行编辑等。http://localhost:4444/api-a/add?a=1&b=2 没有进行路由
http://localhost:4444/api-a/add?a=1&b=2&accessToken 进行路由
获取源码:3--SpringCloud网关zuul