SpringCloud系列:利用Zuul实现统一服务网关服务,简单实现IP白名单功能


声明:本文转载自https://my.oschina.net/gengkangkang/blog/1589285,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

一、概述

上篇文章我们介绍了基于Eureka注册服务提供者和消费者,使用Feign、Ribbon、Hystrix实现服务间的调用、负载均衡及服务熔断和降级功能。本文将基于上述服务,利用SpringCloud Zull实现对外的统一网关服务,可在网关服务内实现签名验证、IP过滤、统一转发等功能,同时基于Hystrix实现对服务的监控功能

二、功能开发实现

1.创建统一网关服务

 创建普通的SpringBoot项目gateway,在pom.xml文件中增加如下依赖

<dependencies>    <dependency>       <groupId>org.springframework.cloud</groupId>       <artifactId>spring-cloud-starter-eureka</artifactId>    </dependency>    <dependency>       <groupId>org.springframework.cloud</groupId>       <artifactId>spring-cloud-starter-zuul</artifactId>    </dependency>    <dependency>       <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-web</artifactId>    </dependency>     <dependency>       <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-test</artifactId>       <scope>test</scope>    </dependency> </dependencies>

在GatewayApplication主方法上添加@EnableZuulProxy注解,启用服务路由功能。查看@EnableZuulProxy源码可知,其包含了服务注册和断路保护注解功能

@EnableCircuitBreaker @EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ZuulProxyMarkerConfiguration.class) public @interface EnableZuulProxy { }

2.服务路由配置

Spring Cloud Zuul通过与 Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要像传统路由配置方式那样为serviceId去指定具体的服务实例地址,只需要通过一组zuul.routes.<route>.pathzuul.routes.<route>.serviceId参数对的方式配置即可。

修改配置文件,配置转发到eureka-consumer服务的请求路由

spring.application.name=gateway server.port=9103 #注册用IP地址代替服务名 eureka.instance.preferIpAddress=true eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${spring.application.name}:${server.port} eureka.client.serviceUrl.defaultZone=http://10.17.5.45:9911/eureka/,http://10.17.5.46:9912/eureka/  #zuul默认为所有服务开启默认的路由,为了服务安全,此处关闭 zuul.ignored-services=* #自定义服务路由 zuul.routes.eureka-consumer.path=/eureka-consumer/** zuul.routes.eureka-consumer.serviceId=eureka-consumer

启动服务,所有向/eureka-consumer/**的请求都会被转发到服务名为eureka-consumer的服务上去,打开postman测试请求

从日志中可以看出,zuul从DynamicServerListLoadBalancer中根据Ribbon负载均衡算法从服务列表中选取后端服务,然后发送请求,返回结果。

对于面向服务的路由配置,除了使用pathserviceId映射的配置方式之外,还有一种更简洁的配置方式:zuul.routes.<serviceId>=<path>,其中<serviceId>用来指定路由的具体服务名,<path>用来配置匹配的请求表达式。比如上面的例子,它的路由规则等价于上面通过pathserviceId组合使用的配置方式。

zuul.routes.eureka-consumer=/eureka-consumer/**

修改配置文件,重启服务,用postman调用服务,可得同样的路由规则

3.实现IP白名单功能

我们主要利用Zuul的过滤器来实现该功能,其过滤器还可以用来实现统一的签名和验签服务以及其他任何你需要的服务,本例实现简单的IP过滤功能

  新建IPFilter类,继承ZuulFilter,实现其方法

@Component public class IPFilter extends ZuulFilter {      Logger logger= LoggerFactory.getLogger(getClass());      @Override     public String filterType() {         return "pre";     }      @Override     public int filterOrder() {         return 0;     }      @Override     public boolean shouldFilter() {         return true;     }      @Override     public Object run() {         RequestContext ctx= RequestContext.getCurrentContext();         HttpServletRequest req=ctx.getRequest();         String ipAddr=this.getIpAddr(req);         logger.info("请求IP地址为:[{}]",ipAddr);        //配置本地IP白名单,生产环境可放入数据库或者redis中         List<String> ips=new ArrayList<String>();         ips.add("172.0.0.1");          if(!ips.contains(ipAddr)){             logger.info("IP地址校验不通过!!!");             ctx.setResponseStatusCode(401);             ctx.setSendZuulResponse(false);             ctx.setResponseBody("IpAddr is forbidden!");         }         logger.info("IP校验通过。");         return null;     }      /**      * 获取Ip地址      * @param request      * @return      */     public  String getIpAddr(HttpServletRequest request){          String ip = request.getHeader("X-Forwarded-For");         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {             ip = request.getHeader("Proxy-Client-IP");         }         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {             ip = request.getHeader("WL-Proxy-Client-IP");         }         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {             ip = request.getHeader("HTTP_CLIENT_IP");         }         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {             ip = request.getHeader("HTTP_X_FORWARDED_FOR");         }         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {             ip = request.getRemoteAddr();         }         return ip;     } }

重启服务,再次发送请求,本地IP被禁止访问

在上面实现的过滤器代码中,我们通过继承ZuulFilter类并重写了下面的四个方法来实现自定义的过滤器.

  • filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。另外还有“route”、“post”、"error”等类型,具体解释如下
  • /**  * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,  * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.  * We also support a "static" type for static responses see  StaticResponseFilter.  * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)  *  * @return A String representing that type  */ abstract public String filterType();
  • filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
  • shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回true,因此该过滤器对所有请求都会生效,实际运用中我们可以利用该函数来指定过滤器的有效范围。
  • run:过滤器的具体逻辑。IP校验不通过时我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,通过ctx.setResponseBody(body)返回body内容。

三、小结

   Demo源码地址:https://gitee.com/gengkangkang/springcloud.git

本文发表于2017年12月13日 18:33
(c)注:本文转载自https://my.oschina.net/gengkangkang/blog/1589285,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2701 讨论 0 喜欢 1

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1