当前位置:   article > 正文

Gateway(拦截器/路由)入门_gateway 拦截器

gateway 拦截器

1、概述

微服务框架中网关提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流等。网关作为整个系统的访问入口,我们希望外部请求系统服务都需要通过网关访问,禁止通过ip端口直接访问,特别是一些重要的内部服务(外部无法直接访问的服务)
我们要在项目中实现一个拦截器,需要继承两个类:GlobalFilter, Ordered

GlobalFilter:全局过滤拦截器,在gateway中已经有部分实现

Ordered:拦截器的顺序

于是一个简单的拦截器就有了

2、实现

在网关服务添加全局过滤器,拦截请求并将内部密钥设置到请求头中,这个密钥的规则可以选择合适的算法,我这里用的是字符串。
在内部服务实现Filter 接口,拦截接收到的请求,对密钥的合法性做校验,对合法请求放行并拒绝无效请求。
在这里插入图片描述

3、网关模块

添加全局过滤器拦截处理,将密钥放入请求头中,键名为gatewayKey

/**
 * 全局网关
 */
@Component
public class GatewayFilter implements GlobalFilter , Ordered{
   @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String uri = request.getURI().toString();
        //放行登录/注册相关接口
        if (uri.contains("login") || uri.contains("register") || uri.contains("logout")) {
            return chain.filter(exchange);
        }
        //获取业务请求中的Token
        List<String> tokens = request.getHeaders().get("token");

        RebackInfoResult result = new RebackInfoResult();
        String token;
        //判断用户是否登录
        if (tokens != null) {
            token = tokens.get(0);
            //token验证
            result = checkToken(token);
        }
        //Token正确,放行请求
        if (result.getCode() == 200) {
            return chain.filter(exchange);
        }
        response.setStatusCode(HttpStatus.UNAUTHORIZED);//状态码
        String s = JSONObject.toJSONString(result);
        DataBuffer wrap = response.bufferFactory().wrap(s.getBytes());
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        return 0;
    }

    public RebackInfoResult checkToken(String token) {
        //Token为空
        if (token == null || "".equals(token)) {
            return RebackInfoResult.errorResult(HttpCodeEnum.TOKEN_NULL);
        }
        //2)如果token不为空,则校验token是否错误/过期
        try {
            JwtUtil.parseToken(token);
        } catch (Exception e) {
            return RebackInfoResult.errorResult(HttpCodeEnum.TOKEN_ERROR);
        }
        return RebackInfoResult.okResult(HttpCodeEnum.SUCCESS);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

ServerWebExchange是Spring框架中的一个接口,用于表示HTTP请求和响应的上下文。它包含了请求和响应的所有信息,例如请求方法、请求头、请求体、响应状态码、响应头、响应体等。通过ServerWebExchange,可以对请求和响应进行操作和处理。

3.1 AbstractGatewayFilterFactory类

AbstractGatewayFilterFactory类中主要包含两个方法:createFilter()apply()
**createFilter()**方法:
该方法用于创建并返回一个GatewayFilter对象。在创建GatewayFilter对象时,可以传入一些配置参数,以定制化过滤器的行为。例如,可以在createFilter()方法中添加自定义的过滤逻辑,并将其封装到一个GatewayFilter对象中,然后将其返回。
示例代码:

java
public abstract GatewayFilter createFilter(Object... args) throws Exception;
  • 1
  • 2

**apply()**方法:
该方法用于将创建好的GatewayFilter对象应用到请求上。在apply()方法中,会接收一个ClientHttpRequestInterceptor对象作为参数,然后将其应用到请求上。这样,在请求被路由到目标服务之前或之后,就会先执行ClientHttpRequestInterceptor中定义的操作。
示例代码:

java
public abstract ClientHttpRequestInterceptor apply(Object... args);
  • 1
  • 2

通过实现AbstractGatewayFilterFactory类中的这两个方法,可以创建自定义的GatewayFilter,并将其应用到Spring Cloud Gateway中,以对请求和响应进行拦截和处理。

3.2 AbstractGatewayFilterFactory和 GlobalFilter区别

AbstractGatewayFilterFactoryGlobalFilter在Spring Cloud Gateway中都用于实现过滤功能,但它们之间存在一些区别。

作用范围:
GlobalFilter是一个全局过滤器,会应用于所有的路由请求。
AbstractGatewayFilterFactory是用于创建自定义GatewayFilter的抽象类,它封装了一些常见的过滤器配置逻辑,如添加参数、修改请求头等。
实现方式:
GlobalFilter通过实现GlobalFilter接口来在请求被路由到目标服务之前或之后执行一些操作,例如修改请求或响应,记录日志,添加头部信息等。它是全局性的,对所有的路由都起作用,无需为每个路由单独配置。
继承AbstractGatewayFilterFactory类并实现其中的方法可以创建自定义的GatewayFilter。这种方式提供了一种便捷的方式来创建自定义的GatewayFilter,封装了一些常见的过滤器配置逻辑。
总结来说,GlobalFilter是一个全局过滤器,应用于所有的路由请求;而AbstractGatewayFilterFactory是用于创建自定义GatewayFilter的抽象类,它封装了一些常见的过滤器配置逻辑。

4、过滤器执行顺序

过滤器会被执行两次,过滤分为pre和post。

pre:请求前调用。
post:响应结果返回时调用,顺序和pre完全相反。

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
在这里插入图片描述
排序规则如下:

每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链。

跨域问题
跨域问题就是浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。
跨域配置

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

5、服务模块

实现Filter接口,拦截所有请求,对所有请求的合法性做校验

      /**
 * 请求拦截,避免服务绕过接口被直接访问
 */
@Component
@WebFilter(filterName = "BaseFilter",urlPatterns = {"/user/**"})
public class BaseFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init filter");
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("进入过滤器========");
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        String gateway = request.getHeader("gatewayKey");
        if(gateway == null || gateway.equals("") || !gateway.equals("key")){
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
 
    @Override
    public void destroy() {
        System.out.println("destroy filter");
    }
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

6、服务之间请求传递请求头

实现RequestInterceptor接口,将请求放入请求头中,往下传递密钥

@Configuration
public class FeignConfiguration implements RequestInterceptor {
 
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取request请求头信息,传递给下一层
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);
            }
        }
        // 独立设置参数
        template.header("token","tokenKey");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

以上就是通过密钥校验的方式避免各个服务被直接访问的基本实现了。

7、 代码结构优化

上面的实现需要在每个微服务中实现,对于这部分重复的代码,可以抽象提取到公用服务模块,其他服务按需引入,是否开启网关拦截可通过注解控制。

网关拦截注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({GatewayFilter.class})
@Inherited
public @interface EnableGatewayFilter {
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
public class GatewayFilter implements Filter {
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        System.out.println("init gateway filter");
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        String gateway = request.getHeader(GatewayFilterConstant.FILTER_KEY_NAME);
        if(gateway == null || gateway.equals("") || !gateway.equals(GatewayFilterConstant.FILTER_KEY_SECRET)){
            System.out.println("======无权访问=======");
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
 
    @Override
    public void destroy() {
        Filter.super.destroy();
        System.out.println("destroy gateway filter");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

密钥传递注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({CommunicationInterceptor.class})
@Inherited
public @interface EnableInnerCommunication {
}
public class CommunicationInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        // 独立设置参数
        template.header(GatewayFilterConstant.FILTER_KEY_NAME,GatewayFilterConstant.FILTER_KEY_SECRET);
    }
}
 组合注解(网关了解+密钥传递)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@EnableInnerCommunication
@EnableGatewayFilter
public @interface EnableGatewayCommunication {
}
实际使用
@SpringBootApplication
@EnableDiscoveryClient
@EnableGatewayFilter
public class ServiceBasicApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceBasicApplication.class, args);
        System.out.println("=========启动成功========");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

这样就可以通过注解的方式灵活的设置服务是否必须通过网关访问。

思考总结
上述的方案在保障密钥安全的情况下,你的底层内部服务是不会被直接访问的

你也可以设置一定的加解密规则(MD5+时效校验,让密钥具有时效性),保障你的服务安全。

另外,可以对于内部服务,可以设置一定的URL规则,例如:/private/xxxService,网关统一拦截该/private/**类请求,这样外部在尝试访问内部服务的时候在网关就会被过滤掉

上述的方案是避免你的内部服务IP在不慎暴露的时候(这个时候别人就能尝试请求的内部服务了),所以我们在这些底层服务添加了一层拦截,来鉴权访问者是否有权访问。

这种方案要妥善保管服务间通信的密钥,设置合适的加密规则和时效性。

后端专属技术群
我建了一个后端专属技术群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以交流技术、职位内推、行业探讨为主

图片

资源共享,共同进步
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号