过滤器类型和请求的生命周期
Zuul 中的过滤器跟我们之前使用的 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。
而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。
1)pre
可以在请求被路由之前调用。适用于身份认证的场景,认证通过后再继续执行下面的流程。
2)route
在路由请求时被调用。适用于灰度发布场景,在将要路由的时候可以做一些自定义的逻辑。
3)post
在 route 和 error 过滤器之后被调用。这种过滤器将请求路由到达具体的服务之后执行。适用于需要添加响应头,记录响应日志等应用场景。
4)error
处理请求时发生错误时被调用。在执行过程中发送错误时会进入 error 过滤器,可以用来统一记录错误信息。
通过上面的图可以清楚地知道整个执行的顺序,请求发过来首先到 pre 过滤器,再到 routing 过滤器,最后到 post 过滤器,任何一个过滤器有异常都会进入 error 过滤器。
通过 com.netflix.zuul.http.ZuulServlet 也可以看出完整执行顺序,ZuulServlet 类似 Spring-Mvc 的 DispatcherServlet,所有的 Request 都要经过 ZuulServlet 的处理。
自定义过滤器需要继承 ZuulFilter,并且需要实现下面几个方法:
1)shouldFilter
是否执行该过滤器,true 为执行,false 为不执行,这个也可以利用配置中心来实现,达到动态的开启和关闭过滤器。
2)filterType
过滤器类型,可选值有 pre、route、post、error。
3)filterOrder
过滤器的执行顺序,数值越小,优先级越高。
4)run
执行自己的业务逻辑,本段代码中是通过判断请求中是否有token,决定是否进行拦截。当token为空时,通过设置 requestContext.setSendZuulResponse(false),告诉 Zuul 不需要将当前请求转发到后端的服务了。通过 setResponseBody 返回数据给客户端。
通过上面的铺垫了解,下面正试开始用Zuul网关ZuulFilter过滤器实现登录鉴权
Zuul 网关服务项目的环境搭建,
可参考博文 [技术陈旧] SpringCloud-Zuul网关项目 环境搭建和整合Eureka访问_springcloud创建一个zuul网关服务,实现通过网关访问eureka client中的接口-CSDN博客
项目核心代码,Zuul过滤器代码
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 登录过滤器
*/
//这个注解不要忘了,加入spring容器中
@Component
public class LoginFilter extends ZuulFilter {
/**
* 过滤器类型
* org.springframework.cloud.netflix.zuul.filters.support.FilterConstants 这个类
* pre:在请求被路由(转发)之前调用
* route:在路由(请求)转发时被调用
* error:服务网关发生异常时被调用
* post:在路由(转发)请求后调用
* @return
*/
@Override
public String filterType() {
//前置过滤器
return PRE_TYPE;
}
/**
* 过滤器顺序,越小越先执行
* @return
*/
@Override
public int filterOrder() {
return 4;
}
/**
* 过滤器是否生效
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
System.out.println(request.getRequestURI()); ///apigateway/product/api/v1/product/list
System.out.println(request.getRequestURL()); //http://localhost:9000/apigateway/product/api/v1/product/list
//ACL
//进行拦截,就会进入下面的 run方法中
if (request.getRequestURI().contains("/apigateway/orderapi/ordermapping/savedingdan")){
return true;
}
//不拦截,放行
return false;
}
/**
* 业务逻辑
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
//获取上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//获取request对象
HttpServletRequest request = requestContext.getRequest();
//token对象
String token = request.getHeader("token");
if(StringUtils.isBlank((token))){
token = request.getParameter("token");
}
//登录校验逻辑 根据公司情况自定义 JWT
//token为空,就不能访问
if (StringUtils.isBlank(token)) {
//停止访问,并返回出错的消息
requestContext.setSendZuulResponse(false);
//防止中文乱码
requestContext.getResponse().setContentType("text/html;charset=UTF-8");
//设置返回的状态码和正文
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
requestContext.setResponseBody("userToken is null");
}
//正常的话,继续向下走
return null;
}
}