Spring Cloud Gateway统一拦截服务请求,避免绕过网关请求服务

概述

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

实现思路

        请求服务添加密钥传递验证,通过网关请求的服务会生成一串密钥,这个密钥会向下游服务彻底,下游服务在接收到请求的时候会先验证密钥的合法性,如未携带密钥或密钥不合法则拒绝响应,以此来达到避免各个微服务绕过网关被直接访问。

Spring Cloud Gateway统一拦截服务请求,避免绕过网关请求服务_第1张图片

基本实现

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

网关模块

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

/**
 * 全局网关
 */
@Component
public class GatewayFilter implements GlobalFilter {
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        PathContainer pathContainer = request.getPath().pathWithinApplication();
        // 添加gatewayKey,防止下游接口直接被访问
        ServerHttpRequest.Builder mutate = request.mutate();
        mutate.header("gatewayKey", "key");
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }
}

服务模块

        实现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");
    }

}

服务之间请求传递请求头

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

@Configuration
public class FeignConfiguration implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取request请求头信息,传递给下一层
        Enumeration 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");
    }
}

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

代码结构优化

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

网关拦截注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({GatewayFilter.class})
@Inherited
public @interface EnableGatewayFilter {

}
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");
    }
}

密钥传递注解 

@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("=========启动成功========");
    }
}

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

思考总结

        上述的方案在保障密钥安全的情况下,你的底层内部服务是不会被直接访问的,当然,你自己把密钥告诉别人就没办法了~~

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

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

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

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

你可能感兴趣的:(Spring干货系列,网关,gateway,spring,cloud)