Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务,是一个基于JVM路由和服务端的负载均衡器,在Spring Cloud框架中,Zuul的角色是网关,负责接收所有REST请求,然后进行内部转发,是微服务提供者集群的流量入口。
路由:将不同REST请求转发至不同的微服务提供者,其作用类似于Nginx的反向代理。同时,也起到了统一端口的作用,将很多微服务提供者的不同端口统一到了Zuul的服务端口
认证:网关直接暴露在公网上时,终端要调用某个服务,通常会把登录后的token(令牌)传过来,网关层对token进行有效性验证。如果token无效(或没有token),就不允许访问REST服务。可以结合Spring Security中的认证机制完成Zuul网关的安全认证
限流:高并发场景下瞬时流量不可预估,为了保证服务对外的稳定性,限流成为每个应用必备的一道安全防火墙。如果没有这道安全防火墙,那么请求的流量超过服务的负载能力时很容易造成整个服务的瘫痪
负载均衡:在多个微服务提供者之间按照多种策略实现负载均衡
Zuul作为网关层微服务,跟其他服务提供者一样都注册在Eureka Server上,可以相互发现。Zuul能感知到哪些服务Provider实例在线,同时通过配置路由规则可以将REST请求自动转发到指定的后端微服务提供者
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
在启动类中添加注解@EnableZuulProxy,声明这是一个网关服务提供者
server:
port: 8810
spring:
application:
name: cloud-zuul
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
ip-address: ${spring.cloud.client.ip-address}
instance-id: ${spring.cloud.client.ip-address}:${server.port}
zuul:
routes:
cloud-provider:
path: /cloud-provider/**
serviceId: cloud-provider
依次启动服务Eureka Server、Provider和Zuul,在浏览器地址输入http://localhost:8770/provider/getProviderInfo/world
再次在浏览器地址栏输入http://localhost:8810/cloud-provider/provider/getProviderInfo/world
路由规则通常有两种方式,其一是路由到直接URL,其二是路由到微服务提供者
两种方式的区别如下:
防止请求头泄露的方式之一是,在Zuul的路由配置中指定要忽略的请求头列表,并且多个敏感头部之间可以用逗号隔开,默认情况,Zuul转发请求会把header清空,如果在微服务集群内部转发请求,上游Provider就会收不到任何头部,如果需要传递原始的header信息到最终的上游,就需要添加如下敏感头部设置
zuul:
routes:
cloud-provider:
sensitiveHeaders:
如何需要屏蔽头信息,需要如下配置
zuul:
routes:
cloud-provider:
sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
默认情况下Zuul会去掉路由的路径前缀,如果上游微服务提供者没有配置路径前缀,Zuul这种默认处理和转发就不会有问题,如果上游微服务提供者配置了统一的路径前缀,前缀去掉后,上游服务提供者就会报404错误,找不到URL对应的资源服务。可以设置配置项stripPrefix的值为false
zuul:
routes:
cloud-provider:
path: /cloud-provider/**
serviceId: cloud-provider
stripPrefix: true #是否取消请求前缀
如果需要对访问网关的所有请求都加上前缀,可以设置配置prefix,具体配置如下:
zuul:
prefix: /native #所有请求添加前缀
routes:
cloud-provider:
path: /cloud-provider/**
serviceId: cloud-provider
stripPrefix: true #是否取消请求前缀
sensitiveHeaders: Cookie,Set-Cookie,token,backend,Authorization
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
management:
endpoints:
web:
exposure:
include: 'routes' #开启查看路由端口
重新启动服务,在浏览器地址栏输入http://localhost:8810/actuator/routes
通过定义过滤器来实现请求的拦截和过滤
pre类型过滤器
请求路由之前调用,用于实现身份验证、记录调试信息
route类型过滤器
发送请求到上游服务,例如使用Apache HttpClient或Netflix Ribbon请求上游服务
post类型过滤器
上游服务返回之后调用,为响应添加HTTP响应头、收集统计信息和指标、将响应回复给客户端
error类型过滤器
在其他阶段发生错误时执行
Zuul提供一个过滤器ZuulFilter抽象基类,可以作为自定义过滤器的父类,需要实现的方法主要有4个
filterType方法
返回自定义过滤器类型,以常量的形式定义在FilterConstants类中
filterOrder方法
返回过滤器顺序,值越小优先级越高
shouldFilter方法
返回过滤器是否生效,返回true表示生效,返回false表示不生效
run方法
过滤器业务逻辑处理,可以进行当前的请求拦截和参数定制,后续路由定制,返回结果定制
例如可以使用前置过滤器打印日志和黑名单处理过滤,具体代码如下
@Component
public class BlackListFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(BlackListFilter.class);
static List<String> blackList = Arrays.asList("");
/**
* 过滤器类型pre:过滤之前;routing:路由之时;post:路由之后;error:发送错误调用
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤执行次序
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否执行过滤
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
if (!context.sendZuulResponse()) {
return false;
}
//返回true,表示需要执行run方法
HttpServletRequest request = context.getRequest();
if (request.getRequestURI().startsWith("/native")) {
return true;
}
return false;
}
/**
* 过滤器具体执行方法
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String host = request.getRemoteHost();
String method = request.getMethod();
String uri = request.getRequestURI();
logger.info("=====>Remote host:{},method:{},uri:{}", host, method, uri);
String username = request.getParameter("username");
if (null != username && blackList.contains(username)) {
logger.info(username + " is forbidden: " + request.getRequestURL().toString());
context.setSendZuulResponse(false);
try {
context.getResponse().setContentType("text/html;charset=utf-8");
context.getResponse().getWriter().write("对不起,您已进入黑名单!");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
return null;
}
}