环境:
jdk1.8;spring boot2.0.3;spring cloud(Finchley.RELEASE版本);Maven3.3
摘要说明:
前面几章我们实现了:
服务中心提供服务注册和发现;
实现消费者对提供方的负载均衡调用;
实现服务调用时的熔断机制使服务更加健全及其监控;
但消费者如何给外部调用且对消费者的集群如何做到负载均衡及权限管理?
故抽象出服务网关这个概念:
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它应还具备了权限控制等功能。
Spring Cloud Zuul:为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
步骤:
1.创建zuul(Zuul服务路由)子项目
通过SPRING INITIALIZR
工具选择Cloud Routing:Zuul模块构建turbine子项目引入依赖(pom.xnl):
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>pers.cc</groupId>
<artifactId>springCloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>zuul</artifactId>
<name>zuul</name>
<description>zuul(服务网关)</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
2.配置zuul
使用@EnableZuulProxy注解开启zuul的服务网关配置:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import pers.cc.springCloud.zuul.filter.AccessTokenFilter;
import pers.cc.springCloud.zuul.filter.AccessTokenFilter1;
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
// 配置过滤器
@Bean
public AccessTokenFilter accessTokenFilter() {
return new AccessTokenFilter();
}
// 配置多个过滤器
@Bean
public AccessTokenFilter1 accessTokenFilter1() {
return new AccessTokenFilter1();
}
}
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
// 配置过滤器
@Bean
public AccessTokenFilter accessTokenFilter() {
return new AccessTokenFilter();
}
// 配置多个过滤器
@Bean
public AccessTokenFilter1 accessTokenFilter1() {
return new AccessTokenFilter1();
}
}
配置application.properties:
#指定服务名和端口
spring.application.name=zuul
server.port=8001
#注册到注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
#单例配置
zuul.routes.hystrix.path=/hystrix/**
zuul.routes.hystrix.url=http://localhost:5001/
#集群配置
zuul.routes.ribbon.path=/ribbon/**
zuul.routes.ribbon.serviceId=ribbon-consumer
#若服务名称和通配url一致则可省略成以下配置
zuul.routes.feign-consumer=/feign-consumer/**
3.开发zuul
a.路由配置
通过在application.properties进行规则配置达到配置路由的效果:
1.单例配置,即消费者只部署一个服务时:
zuul.routes.XX.path=通配地址
zuul.routes.XX.url=服务指向
2.集群配置(自定义path),即后端服务多个且自定义path:
zuul.routes.XX.path=通配地址
zuul.routes.XX.serviceId=服务名称
3.集群配置(默认path),即后端服务多个且默认path:
zuul.routes.XX=/XX/**
b.过滤器开发
过滤器主要是用来进行权限管理;即通过过滤器来进行请求的拦截和过滤;过滤器中可以进行鉴权、签名校验、权限验证等
1)实现过滤器,继承实现ZuulFilter,实现其四个方法:
Object run():主过滤逻辑处理
boolean shouldFilter():判断该过滤器是否需要被执行
int filterOrder():过滤器的执行顺序
String filterType():过滤器类型:pre-可以在请求被路由之前调用、route-在路由请求时候被调用、 post-在route和error过滤器之后被调用、error-处理请求时发生错误时被调用
新建AccessTokenFilter设置需要添加参数accessToken,执行顺序为1;
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
public class AccessTokenFilter extends ZuulFilter {
// run:过滤器的具体逻辑。
// 通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,
// 然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,
// 也可以进一步优化比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Object accessToken = request.getParameter("accessToken");
System.out.println("accessToken:" + accessToken);
if (accessToken == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
return null;
}
// shouldFilter:判断该过滤器是否需要被执行。
// true表示该过滤器对所有请求都会生效。
// 实际运用中我们可以利用该函数来指定过滤器的有效范围。
@Override
public boolean shouldFilter() {
return true;
}
// filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
@Override
public int filterOrder() {
return 0;
}
// filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
// pre:可以在请求被路由之前调用
// route:在路由请求时候被调用
// post:在route和error过滤器之后被调用
// error:处理请求时发生错误时被调用
@Override
public String filterType() {
return "pre";
}
}
新建AccessTokenFilter1设置需要添加参数accessToken且参数不能等于zuul,执行顺序为2;
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
public class AccessTokenFilter1 extends ZuulFilter {
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Object accessToken = request.getParameter("accessToken");
System.out.println("accessToken1:" + accessToken);
if (accessToken == null || "zuul".equals(accessToken)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 2;
}
@Override
public String filterType() {
return "pre";
}
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Object accessToken = request.getParameter("accessToken");
System.out.println("accessToken1:" + accessToken);
if (accessToken == null || "zuul".equals(accessToken)) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return 2;
}
@Override
public String filterType() {
return "pre";
}
}
2)在ZuulApplication里挂载过滤器:
// 配置过滤器
@Bean
public AccessTokenFilter accessTokenFilter() {
return new AccessTokenFilter();
}
// 配置多个过滤器
@Bean
public AccessTokenFilter1 accessTokenFilter1() {
return new AccessTokenFilter1();
}
4.测试zuul
先后启动服务:
服务注册中心(eurekaServer):eureka-server(1001)
服务提供者(eurekaDiscovery):eureka-client(2001)
服务消费者(ribbon):ribbon-consumer(3001)
服务消费者(ribbon):ribbon-consumer(3002)
服务消费者(feign):feign-consumer(4001)
服务消费者(feign):feign-consumer(4002)
服务消费者(hystrix):hystrix-consumer(5001)
服务网关(zuul):zuul(8001)
分别测试上述配置
http://localhost:8001/hystrix/test --》返回401异常且两个过滤器都执行过
http://localhost:8001/hystrix/test?accessToken=11 --》正常返回
http://localhost:8001/ribbon/test?accessToken=11 --》正常返回
http://localhost:8001/feign-consumer/test?accessToken=11 --》正常返回
http://localhost:8001/feign-consumer/test?accessToken=zuul --》返回401异常