一、概述
上篇文章我们介绍了基于Eureka注册服务提供者和消费者,使用Feign、Ribbon、Hystrix实现服务间的调用、负载均衡及服务熔断和降级功能。本文将基于上述服务,利用SpringCloud Zull实现对外的统一网关服务,可在网关服务内实现签名验证、IP过滤、统一转发等功能,同时基于Hystrix实现对服务的监控功能
二、功能开发实现
1.创建统一网关服务
创建普通的SpringBoot项目gateway,在pom.xml文件中增加如下依赖
org.springframework.cloudspring-cloud-starter-eurekaorg.springframework.cloudspring-cloud-starter-zuulorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-testtest
在GatewayApplication主方法上添加@EnableZuulProxy注解,启用服务路由功能。查看@EnableZuulProxy源码可知,其包含了服务注册和断路保护注解功能
@EnableCircuitBreaker@EnableDiscoveryClient@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Import(ZuulProxyMarkerConfiguration.class)public@interfaceEnableZuulProxy {}
2.服务路由配置
Spring Cloud Zuul通过与 Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要像传统路由配置方式那样为serviceId去指定具体的服务实例地址,只需要通过一组与参数对的方式配置即可。
修改配置文件,配置转发到eureka-consumer服务的请求路由
spring.application.name=gatewayserver.port=9103#注册用IP地址代替服务名eureka.instance.preferIpAddress=trueeureka.instance.instance-id=$:$:$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负载均衡算法从服务列表中选取后端服务,然后发送请求,返回结果。
对于面向服务的路由配置,除了使用path和serviceId映射的配置方式之外,还有一种更简洁的配置方式:,其中用来指定路由的具体服务名,用来配置匹配的请求表达式。比如上面的例子,它的路由规则等价于上面通过与组合使用的配置方式。
zuul.routes.eureka-consumer=/eureka-consumer/**
修改配置文件,重启服务,用postman调用服务,可得同样的路由规则
3.实现IP白名单功能
我们主要利用Zuul的过滤器来实现该功能,其过滤器还可以用来实现统一的签名和验签服务以及其他任何你需要的服务,本例实现简单的IP过滤功能
新建IPFilter类,继承ZuulFilter,实现其方法
@ComponentpublicclassIPFilterextendsZuulFilter{ Logger logger= LoggerFactory.getLogger(getClass());@OverridepublicStringfilterType(){return"pre"; }@OverridepublicintfilterOrder(){return; }@OverridepublicbooleanshouldFilter(){returntrue; }@OverridepublicObjectrun(){ RequestContext ctx= RequestContext.getCurrentContext(); HttpServletRequest req=ctx.getRequest(); String ipAddr=this.getIpAddr(req); logger.info("请求IP地址为:[{}]",ipAddr);//配置本地IP白名单,生产环境可放入数据库或者redis中List ips=newArrayList(); 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校验通过。");returnnull; }/** * 获取Ip地址 *@paramrequest *@return*/publicStringgetIpAddr(HttpServletRequest request){ String ip = request.getHeader("X-Forwarded-For");if(ip ==nullip.length() =="unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); }if(ip ==nullip.length() =="unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); }if(ip ==nullip.length() =="unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); }if(ip ==nullip.length() =="unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); }if(ip ==nullip.length() =="unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); }returnip; }}
重启服务,再次发送请求,本地IP被禁止访问
在上面实现的过滤器代码中,我们通过继承ZuulFilter类并重写了下面的四个方法来实现自定义的过滤器.
:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为,代表会在请求被路由之前执行。另外还有“route”、“post”、"error”等类型,具体解释如下
/** * to classify a filter bytype. Standard typesinZuul are"pre"forpre-routing filtering, *"route"forrouting to an origin,"post"forpost-routing filters,"error"forerrorhandling. * We also support a"static"typeforstatic responses see StaticResponseFilter. * Any filterType made be createdoraddedandrun by calling FilterProcessor.runFilters(type) * * @returnA String representing thattype*/abstract public String filterType();
:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
:判断该过滤器是否需要被执行。这里我们直接返回true,因此该过滤器对所有请求都会生效,实际运用中我们可以利用该函数来指定过滤器的有效范围。
:过滤器的具体逻辑。IP校验不通过时我们通过令zuul过滤该请求,不对其进行路由,然后通过设置了其返回的错误码,通过返回body内容。
三、小结
Demo源码地址:https://gitee.com/gengkangkang/springcloud.git
博客地址:https://my.oschina.net/gengkangkang/blog/1589285
领取专属 10元无门槛券
私享最新 技术干货