微服务保护

我们设计所有客户端请求都是通过微服务网关转发完成的,虽然我们约定如此,但是还是可以直接通过访问微服务地址的方式来获取服务

为了解决这个问题,我们可以自定义Zuul过滤器

1. 微服务保护设计思路

在网关转发请求前,请求头部加入网关信息,然后在处理请求的微服务模块里定义全局拦截器,校验请求头部的网关信息,这样就能避免客户端直接访问微服务

2. zuul的4种核心过滤器

image-20191129004026595

这4种过滤器处于不同的生命周期,所以其职责也各不相同

  • PRE: PRE过滤器用于将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址,并做一些前置加工,比如请求的校验等
  • ROUTING: ROUTING 过滤器用于将外部请求转发到具体服务实例上去
  • POST: POST 过滤器用于将微服务的响应信息返回到客户端,这个过程可以对返回数据进行加工处理
  • ERROR: 上述的过程发生异常后将调用ERROR 过滤器。ERROR 过滤器捕获到异常后需要将异常信息返回给客户端,所以最终还是会调用POST过滤器

Spring Cloud Zuul为各个生命周期阶段实现了一批过滤器,如下所示:

image-20191129004534679

这些过滤器的优先级和作用如下表所示:

生命周期 优先级 过滤器 描述
pre -3 ServletDetectionFilter 标记处理Servlet的类型
pre -2 Servlet30WrapperFilter 包装HttpServletRequest请求
pre -1 FormBodyWrapperFilter 包装请求体
route 1 DebugFilter 标记调试标志
route 5 PreDecorationFilter 处理请求上下文供后续使用
route 10 RibbonRoutingFilte serviceId请求转发
route 10 SimpleHostRoutingFilter url请求转发
route 50 SendForwardFilter forward请求转发
post 0 SendErrorFilter 处理有错误的请求响应
post 10 SendResponseFilter 处理正常的请求响应

从上面的表格可以看到,PreDecorationFilter用于处理请求上下文,优先级为5,所以我们可以定义一个优先级在PreDecorationFilter之后的过滤器,这样便可以拿到请求上下文。

3. 设计微服务保护

3.1 自定义zuul过滤器

@Slf4j
@Component
public class GatewayRequestFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 6;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY);
        HttpServletRequest request = ctx.getRequest();
        String host = request.getRemoteHost();
        String method = request.getMethod();
        String uri = request.getRequestURI();

        log.info("请求URI:{},HTTP Method:{},请求IP:{},ServerId:{}", uri, method, host, serviceId);

        byte[] token = Base64Utils.encode(("febs:zuul:123456").getBytes());
        ctx.addZuulRequestHeader("ZuulToken", new String(token));
        return null;
    }
}

自定义Zuul过滤器需要继承ZuulFilter,并实现它的四个抽象方法:

  1. filterType,对应Zuul生命周期的四个阶段:pre、post、route和error,我们要在请求转发出去前添加请求头,所以这里指定为pre;
  2. filterOrder,过滤器的优先级,数字越小,优先级越高。PreDecorationFilter过滤器的优先级为5,所以我们可以指定为6让我们的过滤器优先级比它低;
  3. shouldFilter,方法返回boolean类型,true时表示是否执行该过滤器的run方法,false则表示不执行;
  4. run,定义过滤器的主要逻辑。这里我们通过请求上下文RequestContext获取了转发的服务名称serviceId和请求对象HttpServletRequest,并打印请求日志。随后往请求上下文的头部添加了Key为ZuulToken,Value为febs:zuul:123456的信息。这两个值可以抽取到常量类中。

定义好Zuul过滤器后,我们需要在各个微服务里定义一个全局拦截器拦截请求,并校验Zuul Token。

3.2 校验Zuul Token

public class FebsServerProtectInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        // 从请求头中获取 Zuul Token
        String token = request.getHeader(FebsConstant.ZUUL_TOKEN_HEADER);
        String zuulToken = new String(Base64Utils.encode(FebsConstant.ZUUL_TOKEN_VALUE.getBytes()));
        // 校验 Zuul Token的正确性
        if (StringUtils.equals(zuulToken, token)) {
            return true;
        } else {
            FebsResponse febsResponse = new FebsResponse();
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.getWriter().write(JSONObject.toJSONString(febsResponse.message("请通过网关获取资源")));
            return false;
        }
    }

FebsServerProtectInterceptor实现了HandlerInterceptorpreHandle方法,该拦截器可以拦截所有Web请求。在preHandle方法中,我们通过HttpServletRequest获取请求头中的Zuul Token,并校验其正确性,当校验不通过的时候返回403错误。

results matching ""

    No results matching ""