springcloud 网关_Spring Cloud 系列之 Netflix Zuul 服务网关(二)

287888448c3cb2c192f7dff0fa6af34a.png

本篇文章为系列文章,未读第一集的同学请猛戳这里:

哈喽沃德先生:Spring Cloud 系列之 Netflix Zuul 服务网关(一)​zhuanlan.zhihu.com
df7a693a546b051ed47ecab6f070e6c8.png

本篇文章讲解 Zuul 网关过滤器实现统一鉴权以及网关过滤器异常统一处理。

网关过滤器

20aebf9e6dae249c03924d46591a03cb.png
https://www.zhihu.com/video/1234207041360777216

76d986b2d361d2d138c8c03d20d6734c.png

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

访问:http://localhost:9000/product-service/product/1?token=abc123 结果如下:

cce2698a8de7763246ec2a2a1904bd7f.png

Zuul 请求的生命周期

8468dfdacb544ba14ecd0c304199def7.png
  1. HTTP 发送请求到 Zuul 网关
  2. Zuul 网关首先经过 pre filter
  3. 验证通过后进入 routing filter,接着将请求转发给远程服务,远程服务执行完返回结果,如果出错,则执行 error filter
  4. 继续往下执行 post filter
  5. 最后返回响应给 HTTP 客户端

网关过滤器异常统一处理

470ee5ca2e8d64bcbd979e1aaf392bbc.png
https://www.zhihu.com/video/1234207208662945792

创建过滤器

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
下一篇我们讲解 Zuul 和 Hystrix 的无缝结合,实现网关监控、网关熔断、网关限流、网关调优,记得关注噢~

66e3392d9d987ff0da6553eb478489b6.gif

大家可以通过 分类 查看更多关于 Spring Cloud 的文章。

本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议

您的点赞转发是对我最大的支持。

扫码关注 哈喽沃德先生「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~

595b6a3d92da93afb4ae2c00236d26f4.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值