![287888448c3cb2c192f7dff0fa6af34a.png](https://i-blog.csdnimg.cn/blog_migrate/9a8bfa1dbc8dca861dc89966c15a2fc8.jpeg)
本篇文章为系列文章,未读第一集的同学请猛戳这里:
哈喽沃德先生:Spring Cloud 系列之 Netflix Zuul 服务网关(一)zhuanlan.zhihu.com![df7a693a546b051ed47ecab6f070e6c8.png](https://i-blog.csdnimg.cn/blog_migrate/924a808c3082926bc99a3c6dbb027a3b.jpeg)
本篇文章讲解 Zuul 网关过滤器实现统一鉴权以及网关过滤器异常统一处理。
网关过滤器
![20aebf9e6dae249c03924d46591a03cb.png](https://i-blog.csdnimg.cn/blog_migrate/fcc2fc107528d1fcef857accad37152d.png)
![76d986b2d361d2d138c8c03d20d6734c.png](https://i-blog.csdnimg.cn/blog_migrate/8761be3325aad8a0e01648ae05d9feea.jpeg)
Zuul 包含了对请求的路由和过滤两个核心功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。然而实际上,路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。
路由映射主要通过 pre
类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由 routing
类型的过滤器来完成,对 pre
类型过滤器获得的路由地址进行转发。所以说,过滤器可以说是 Zuul 实现 API 网关功能最核心的部件,每一个进入 Zuul 的 http 请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
关键名词
- 类型:定义路由流程中应用过滤器的阶段。共 pre、routing、post、error 4 个类型。
- 执行顺序:在同类型中,定义过滤器执行的顺序。比如多个 pre 类型的执行顺序。
- 条件:执行过滤器所需的条件。true 开启,false 关闭。
- 动作:如果符合条件,将执行的动作。具体操作。
过滤器类型
- pre:请求被路由到源服务器之前执行的过滤器
- 身份认证
- 选路由
- 请求日志
- routing:处理将请求发送到源服务器的过滤器
- post:响应从源服务器返回时执行的过滤器
- 对响应增加 HTTP 头
- 收集统计和度量指标
- 将响应以流的方式发送回客户端
- error:上述阶段中出现错误时执行的过滤器
入门案例
创建过滤器
Spring Cloud Netflix Zuul 中实现过滤器必须包含 4 个基本特征:过滤器类型,执行顺序,执行条件,动作(具体操作)。这些步骤都是 ZuulFilter
接口中定义的 4 个抽象方法:
package com.example.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* 网关过滤器
*/
@Component
public class CustomFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class);
/**
* 过滤器类型
* pre
* routing
* post
* error
*
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 执行顺序
* 数值越小,优先级越高
*
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 执行条件
* true 开启
* false 关闭
*
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 动作(具体操作)
* 具体逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
// 获取请求上下文
RequestContext rc = RequestContext.getCurrentContext();
HttpServletRequest request = rc.getRequest();
logger.info("CustomFilter...method={}, url={}",
request.getMethod(),
request.getRequestURL().toString());
return null;
}
}
filterType
:该函数需要返回一个字符串代表过滤器的类型,而这个类型就是在 http 请求过程中定义的各个阶段。在 Zuul 中默认定义了 4 个不同的生命周期过程类型,具体如下:- pre:请求被路由之前调用
- routing: 路由请求时被调用
- post:routing 和 error 过滤器之后被调用
- error:处理请求时发生错误时被调用
filterOrder
:通过 int 值来定义过滤器的执行顺序,数值越小优先级越高。shouldFilter
:返回一个 boolean 值来判断该过滤器是否要执行。run
:过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续路由,或是在请求路由返回结果之后,对处理结果做一些加工等。
访问
访问:http://localhost:9000/product-service/product/1 控制台输出如下:
CustomFilter...method=GET, url=http://localhost:9000/product-service/product/1
统一鉴权
接下来我们在网关过滤器中通过 token 判断用户是否登录,完成一个统一鉴权案例。
创建过滤器
package com.example.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 权限验证过滤器
*/
@Component
public class AccessFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
// 获取请求上下文
RequestContext rc = RequestContext.getCurrentContext();
HttpServletRequest request = rc.getRequest();
// 获取表单中的 token
String token = request.getParameter("token");
// 业务逻辑处理
if (null == token) {
logger.warn("token is null...");
// 请求结束,不在继续向下请求。
rc.setSendZuulResponse(false);
// 响应状态码,HTTP 401 错误代表用户没有访问权限
rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
// 响应类型
rc.getResponse().setContentType("application/json; charset=utf-8");
PrintWriter writer = null;
try {
writer = rc.getResponse().getWriter();
// 响应内容
writer.print("{"message":"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + ""}");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != writer)
writer.close();
}
} else {
// 使用 token 进行身份验证
logger.info("token is OK!");
}
return null;
}
}
访问
访问:http://localhost:9000/product-service/product/1 结果如下:
![84298af2f6d012494d033c99e9f412fc.png](https://i-blog.csdnimg.cn/blog_migrate/5a65c0b7a4c90ecd2b5987c9e743cefc.png)
访问:http://localhost:9000/product-service/product/1?token=abc123 结果如下:
![cce2698a8de7763246ec2a2a1904bd7f.png](https://i-blog.csdnimg.cn/blog_migrate/07639203e02e9e0544e5c09b2325d045.png)
Zuul 请求的生命周期
![8468dfdacb544ba14ecd0c304199def7.png](https://i-blog.csdnimg.cn/blog_migrate/b42788e679c1af2295ad27b9a4cc46a4.jpeg)
- HTTP 发送请求到 Zuul 网关
- Zuul 网关首先经过 pre filter
- 验证通过后进入 routing filter,接着将请求转发给远程服务,远程服务执行完返回结果,如果出错,则执行 error filter
- 继续往下执行 post filter
- 最后返回响应给 HTTP 客户端
网关过滤器异常统一处理
![470ee5ca2e8d64bcbd979e1aaf392bbc.png](https://i-blog.csdnimg.cn/blog_migrate/c8984c1308f20d15b2b119199b33558f.png)
创建过滤器
package com.example.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 异常过滤器
*/
@Component
public class ErrorFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext rc = RequestContext.getCurrentContext();
Throwable throwable = rc.getThrowable();
logger.error("ErrorFilter..." + throwable.getCause().getMessage(), throwable);
// 响应状态码,HTTP 500 服务器错误
rc.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
// 响应类型
rc.getResponse().setContentType("application/json; charset=utf-8");
PrintWriter writer = null;
try {
writer = rc.getResponse().getWriter();
// 响应内容
writer.print("{"message":"" + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + ""}");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != writer)
writer.close();
}
return null;
}
}
模拟异常
在 pre 过滤器中添加模拟异常代码。
// 模拟异常
Integer.parseInt("zuul");
配置文件
禁用 Zuul 默认的异常处理 filter:SendErrorFilter
zuul:
# 禁用 Zuul 默认的异常处理 filter
SendErrorFilter:
error:
disable: true
访问
访问:http://localhost:9000/product-service/product/1 结果如下:
![3d31323180a1aef01e0c7c38eed150c8.png](https://i-blog.csdnimg.cn/blog_migrate/56cae9de6724c3e3e55ff4b374b1d027.png)
下一篇我们讲解 Zuul 和 Hystrix 的无缝结合,实现网关监控、网关熔断、网关限流、网关调优,记得关注噢~
![66e3392d9d987ff0da6553eb478489b6.gif](https://i-blog.csdnimg.cn/blog_migrate/b2b5766a74e257a3183e4ea0c88195ce.gif)
大家可以通过 分类
查看更多关于 Spring Cloud
的文章。
本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议
。
您的点赞
和转发
是对我最大的支持。
扫码关注 哈喽沃德先生
「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~
![595b6a3d92da93afb4ae2c00236d26f4.gif](https://i-blog.csdnimg.cn/blog_migrate/5afb107072afa258685e512833d3e1cd.gif)