SpringCloud Zuul配置微服务网关详解

7 篇文章 1 订阅
3 篇文章 0 订阅

目录

一、准备搭建环境的目标

二、zuul微服务网关主要配置

三、Zuul微服务网关默认配置的坑

四、重定向无法获取正确路径的填坑过程

五、关于熔断和超时设置的问题


一、准备搭建环境的目标

笔者在https://mp.csdn.net/postedit/82226297,已经介绍了如何配置独立的认证中心和SSO客户端,以及完成配套的资源服务器配置。但是,在实际生产环境中使用会存在一些安全问题。例如所有微服务的真实地址和使用的真实端口都被暴露给了用户,容易被不法之徒进行非法攻击,再对外服务时如果做隔离,对每个服务都需要做单独的配置,无论是通过NGINX代理服务或硬件网关等方式处理,工作都很繁琐,当有成百的微服务部署时,这个工程量可想而知多么浩大。另外,当所有微服务都要在调用前后进行一些通用的处理时,通过微服务网关添加过滤器进行预处理也是极为方便的事。因此利用SpringCloud+Zuul来配置统一的微服务网关,达到上述需求。

 

例如当前有下列相关资源

认证服务端  http://localhost:8000

应用客户端  http://localhost:8080

资源服务端  http://localhost:7779

访问逻辑需下图:

 

使用zuul微服务网关后访问实例

首先可在ZUUL将微服务路径进行映射

认证服务端  http://localhost:8000 ===>  http://localhost/sso

应用客户端  http://localhost:8080 ===>  http://localhost/manage

资源服务端  http://localhost:7779 ===>  http://localhost/product

需要新增加微服务网关的服务

微服务网关 开启默认的80或443端口(实际上访问localhost)

预期的加入微服务网关后使用示例情况如下图:

开启微服务网关后,实现服务实际地址向虚拟地址的映射,所有对虚拟地址的操作均可以透明反应到真实的微服务上。

 

二、zuul微服务网关主要配置

首先是需要引入pom的依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

      <modelVersion>4.0.0</modelVersion>

      <parent>

           <groupId>org.springframework.boot</groupId>

           <artifactId>spring-boot-starter-parent</artifactId>

           <version>1.5.13.RELEASE</version>

           <relativePath></relativePath>

      </parent>

      <artifactId>gateway</artifactId>

      <packaging>jar</packaging>

      <name>gateway</name>

      <properties>

           <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

           <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

           <java.version>1.8</java.version>

      </properties>

      <dependencyManagement>

           <dependencies>

                 <dependency>

                      <groupId>org.springframework.cloud</groupId>

                      <artifactId>spring-cloud-dependencies</artifactId>

                      <version>Dalston.SR5</version>

                      <type>pom</type>

                      <scope>import</scope>

                 </dependency>

           </dependencies>

      </dependencyManagement>

      <dependencies>

           <dependency>

                 <groupId>org.springframework.boot</groupId>

                 <artifactId>spring-boot-starter-thymeleaf</artifactId>

           </dependency>

           <dependency>

                 <groupId>org.springframework.boot</groupId>

                 <artifactId>spring-boot-starter-web</artifactId>

           </dependency>

           <dependency>

                 <groupId>org.springframework.cloud</groupId>

                 <artifactId>spring-cloud-starter-eureka</artifactId>

           </dependency>

           <dependency>

                 <groupId>org.springframework.cloud</groupId>

                 <artifactId>spring-cloud-starter-config</artifactId>

           </dependency>

           <dependency>

                 <groupId>org.springframework.cloud</groupId>

                 <artifactId>spring-cloud-starter-zuul</artifactId>

           </dependency>

           <dependency>

                 <groupId>org.springframework.cloud</groupId>

                 <artifactId>spring-cloud-starter-hystrix</artifactId>

           </dependency>

           <dependency>

                 <groupId>com.google.code.gson</groupId>

                 <artifactId>gson</artifactId>

           </dependency>

      </dependencies>

      <build>

           <plugins>

                 <plugin>

                      <groupId>org.springframework.boot</groupId>

                      <artifactId>spring-boot-maven-plugin</artifactId>

                      <configuration>

                            <fork>true</fork>

                      </configuration>

                 </plugin>

           </plugins>

      </build>

</project>

 

===========接下来编写入口程序===========

 

@SpringBootApplication

@EnableZuulProxy

public class GatwayApplication {

      public static void main(String[] args) {

           SpringApplication.run(GatwayApplication.class, args);

      }

}

很简单,只需要增加一个开启@EnableZuulProxy注解的配置即可

 

============接下来配置zuul的属性配置文件即可============

 

#关闭弹出的默认认证登录框

security.basic.enabled=false

#忽略框架默认的服务映射路径

zuul.ignoredServices='*'

#不忽略框架与权限相关的头信息

zuul.ignoreSecurityHeaders=false

#不忽略任何头部信息,所有header都转发到下游的资源服务器

zuul.sensitiveHeaders=

#以下是自定义服务与路径的映射关系,也可以通过path和url直接映射

zuul.product.serviceId=product

zuul.product.path=/product/**

zuul.sso.serviceId=sso

zuul.sso.path=/sso/**

zuul.manage.serviceId=manage

zuul.manage.path=/manage/**

#网关的session名字,建议每个微服务都单独命名

server.session.cookie.name= GATWAY_SESSION

server.port=80

spring.application.name=gatway

#配置注册中心的地址,以便根据serviceID去发现这些service

eureka.instance.hostname=localhost

eureka.client.serviceUrl.defaultZone=http://localhost:7771/eureka

OK,如此简单,微服务配置既完成了。

如果微服务没有权限验证,通过映射的地址:例如

在浏览器输入http://localhost/manage/index即可访问到http://localhost:8080/index相同的页面信息了。

如下所示访问http://localhost/manage/index

 

在访问http://localhost:8080/index

都返回了正确的、真实的资源页面。访问REST服务接口也是同样。

 

三、Zuul微服务网关默认配置的坑

当访问的微服务某一个页面若发生重定向,重定向会把真实的URL地址和真实的端口暴露在地址栏中,且登录认证成功后也无法正常回调到需要鉴权的应用页面。如下面流转逻辑示例:

我们的理想如下图:

但现实很残酷,实际情况是这样:

查了下网上解决方案,说的要在配置文件中加上如下属性:

zuul.addHostHeader=true

加上上面这个配置后,情况变得更槽糕了,情况如下图逻辑所示

显然这个返回有问题,并没有将当前服务的前缀加载Host后面。

实际上,zuul网关是在这里做了一个向后台实际微服务请求的动作,并重新组装成了返回给客户端的Response。

经过断点发现,当请求的页面发生重定向,并且配置zuul.addHostHeader=true后,返回reponse的header中location地址会直接将真实地址的Host:port替换为网关的Host:port。例如返回的真实地址是localhost:8000/login,会直接被替换为localhost/login。但由于localhost:8000/login实际应该映射到localhost/sso/login,因此造成了返回地址404-Not-Found(但笔者实在没有找到这个替换的动作在哪里执行的)

下面说下笔者如何填这个坑,可能办法有点土,但能解决实际问题。

四、重定向无法获取正确路径的填坑过程

解决思路

微服务网关对后台服务资源的代理请求均是在RibbonRoutingFilter这个过滤器中实现的,具体业务处理方法为

@Override

      public Object run() {

           RequestContext context = RequestContext.getCurrentContext();

           this.helper.addIgnoredHeaders();

           try {

                 RibbonCommandContext commandContext = buildCommandContext(context);

                 ClientHttpResponse response = forward(commandContext);

                 setResponse(response);

                 return response;

           }

           catch (ZuulException ex) {

                 throw new ZuulRuntimeException(ex);

           }

           catch (Exception ex) {

                 throw new ZuulRuntimeException(ex);

           }

      }

只要覆写这个方法,将微服务返回的Response进行处理后再提交给客户端即可。当然覆写后,要用自定义的过滤器来替换掉原来注册的过滤器。

具体改造方案如下:

======首先新建一个过滤器,直接继承自RibbonRoutingFilter=======

 

package ywcai.ls.gateway;

//开启这个注解,zuul会在配置时自动添加至过滤器链

@Component

public class FixRibbonRoutingFilter extends RibbonRoutingFilter{

//实例化过滤器需要的辅助类,在自动化配置中已经有实例,直接注解注入即可

@Autowired

ProxyRequestHelper helper;

//实例化过滤器需要的工厂类,在自动化配置中已经有实例,直接注解注入即可

@Autowired

RibbonCommandFactory<?> ribbonCommandFactory;

//如果重定向之前没有页面,则给一个默认的地址  

String defaultSuccessUrl="/index";

//辅助方法,下面会介绍

      public void setDefaultSuccessUrl(String url)

      {

           if(url.equals("/")||url.equals(""))

           {

                 return ;

           }

           this.defaultSuccessUrl=url.startsWith("/")?url:"/"+url;

      }

//构造类,集成付方法即可

      public FixRibbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) {

           super(helper, ribbonCommandFactory,Collections.emptyList());

           // TODO Auto-generated constructor stub

      }

//辅助方法类,下面会介绍

      private void addPathCache(String requestPath,String requestServiceId )

      {

           if(requestPath.equals("/")||requestPath.equals(""))

           {

                 requestPath=defaultSuccessUrl;

           }

           HttpSession cache=RequestContext.getCurrentContext().getRequest().getSession();

           if(!isHasCache(requestPath))

           {

                 cache.setAttribute(requestPath,requestServiceId);

           }

      }

//辅助方法类,判断是否有缓存

      private boolean isHasCache(String requestPath)

      {

           HttpSession cache=RequestContext.getCurrentContext().getRequest().getSession();

           return cache.getAttribute(requestPath)!=null?true:false;

      }

//辅助方法类,下面会介绍

      private String getServiceIdAndRemove(String requestPath)

      {

           HttpSession cache=RequestContext.getCurrentContext().getRequest().getSession();

           String serviceId="";

           if(isHasCache(requestPath))

           {

                 serviceId= (String) cache.getAttribute(requestPath);

                 cache.removeAttribute(requestPath);

           }

           return serviceId;

      }

//辅助方法类,组装正确的重定向地址

      private void assembleRealPath(ClientHttpResponse response, URI location,

                 String nowPath,String serviceId) {

           int nowPort=location.getPort()<=0?80:location.getPort();

           String newPath=

                      location.getScheme()+"://"+location.getHost()+":"

                                  +nowPort+"/"+serviceId+nowPath;

      newPath=location.getQuery()==null?newPath:(newPath+"?"+location.getQuery());

      newPath=location.getFragment()==null?newPath:(newPath+"#"+location.getFragment());

           URI newLocation=null;

           try {

                 newLocation = new URI(newPath);

           } catch (URISyntaxException e) {

                 // TODO Auto-generated catch block

                 e.printStackTrace();

           }

           response.getHeaders().setLocation(newLocation);

      }

//核心业务逻辑,其他都不变,增加对reponse的处理逻辑即可

      @Override

      public Object run() {

           // TODO Auto-generated method stub

           RequestContext context = RequestContext.getCurrentContext();

           this.helper.addIgnoredHeaders();

           try {

                 RibbonCommandContext commandContext = buildCommandContext(context);

//获取重定向前访问的url的资源路径,这个地址是不包含host的

                 String preUrl=commandContext.getUri();

                 //如果现在被重定向到的是登录页面,则缓存访问前一刻资源的路径和服务ID,并且只缓存记录这个SESSION访问的第一个ServiceID

                 if(preUrl.equals("/login"))

                 {

//记录登录时的serviceID作为默认的serviceID

      addPathCache(defaultSuccessUrl,commandContext.getServiceId());

                 }

                 ClientHttpResponse response = forward(commandContext);

//下面是具体的reponse处理逻辑

                 URI location=response.getHeaders().getLocation(); 

      if(response.getStatusCode()==HttpStatus.FOUND&&location!=null)

                 {    

          

                      String nowPath=location.getPath();  

                      if(nowPath.equals("/login"))

                      {

      //如果是被重定向了,则记录之前的路径

                 String serviceId=commandContext.getServiceId();

                 addPathCache(preUrl,serviceId);

                 assembleRealPath(response,location,nowPath,  serviceId);

                      }

                      else if(isHasCache(nowPath))

                      {

//如果是缓存过这个页面,则获取缓存路径重新封装并重定向到缓存位置

               String serviceId=getServiceIdAndRemove(nowPath);

               assembleRealPath(response,location,nowPath,serviceId);

                      }

                     

                      else if(nowPath.equals("/")||nowPath.equals(""))

                      {

//如果资源是"/"或为空,则代表是直接在浏览器输入login页面登录的,转到默认页面。                         

               String serviceId=getServiceIdAndRemove(defaultSuccessUrl);              
               assembleRealPath(response,location,defaultSuccessUrl,serviceId);

                      }

                      //其他情况则什么也不做

                      else

                      {

                      }

                 }

                 setResponse(response);

                 return response;

           }

           catch (ZuulException ex) {

                 throw new ZuulRuntimeException(ex);

           }

           catch (Exception ex) {

                 throw new ZuulRuntimeException(ex);

           }

      }

}

 

======然后在启动类中将原来的RibbonRoutingFilter剔除======  

 

package ywcai.ls.gateway;

@SpringBootApplication

@EnableZuulProxy

public class GatwayApplication {

      //RibbonRoutingFilter

      public static void main(String[] args) {

           // TODO Auto-generated method stub

           SpringApplication.run(GatwayApplication.class, args);

           removeDefaultRibbonFilter();

      }

      private static void removeDefaultRibbonFilter() {

//只需要删除系统的ribbonRoutingFilter即可,自定义的会自动注入。

           FilterRegistry r=FilterRegistry.instance();

           r.remove("ribbonRoutingFilter");

      }

}

 

==============很简单,看下改造后的效果==============

 

访问localhost/manage/test

经过多次重定向后,最后定向到认证中心的login页面-Localhost/sso/login

 

提交账号密码后,又经过多次认证,重定向。返回到了第一次我想要访问的页面-locahlost/manage/test

 

五、关于熔断和超时设置的问题

设置熔断的时间,如果发生了熔断,默认为进行一次请求重发。

这个配置建议在网关和微服务都需要设置,否则会有一个默认值生效

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=40000

设置ribbon的网络请求超时时间。

ribbon.ReadTimeout=30000

ribbon.ConnectTimeout=30000

建议在网关和微服务也都进行配置

如果本身是微服务A,又访问了其他的微服务B,那这两个配置会对A造成影响,微服务B超时会引起微服务A报超时错误。

总的来说,就是时间设置较小的设置会影响另外的配置。

一般熔断超时时间应该设置的比ribbon的网络请求超时时间长。

 

相关源码请查看作者git   https://github.com/ywcai/  

 

  • 1
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Spring Cloud Zuul是一个基于Netflix Zuul服务网,它提供了动态路由、监控、弹性、安全等功能。在使用Spring Cloud Zuul时,需要进行一些配置,包括路由配置、过滤器配置、负载均衡配置等。具体配置可以参考Spring Cloud官方文档。 ### 回答2: Spring Cloud Zuul是基于Netflix Zuul的一种服务网解决方案。它提供了一种动态路由、监控、弹性、安全性、负载均衡等功能,可以向外暴露API,以及将请求转发给底层的服务。 Zuul可以进行横向扩展,只需添加更多的Zuul实例即可轻松实现。Zuul和Spring Cloud配合使用,可以提供了完整的服务架构解决方案。 在配置Zuul时,需要注意以下几个方面: 1.创建Zuul应用程序: 可以通过使用Spring Initializr轻松创建新的Zuul应用程序。在创建过程中,只需选择相选项即可。例如,在创建Spring Boot应用程序时,可以勾选Zuul选项,即可为您创建一个包含Zuul配置的应用程序。 2.配置Zuul路由: 要配置路由,需要编辑application.yml文件。在该文件中,可以为每个服务配置一个路由规则。例如,如果要将请求转发到特定的服务,则可以配置以下路由规则: ``` zuul: routes: users: /myusers/** ``` 在这个例子中,我们设置了名为“users”的服务,它使用“/myusers/**”作为前缀路径。这意味着所有以“/myusers”开头的请求都将转发到名为“users”的服务。 3.配置Zuul过滤器: Zuul还提供了过滤器,可以用来进行请求和响应处理。使用Zuul过滤器可以实现以下功能: 认证和安全性 审计和日志记录 传输压缩 动态路由 在Zuul中,过滤器是完全可配置的,可以使用Java代码来实现自定义过滤器。 4.配置Zuul负载均衡: Zuul还提供了负载均衡的功能,它可以将请求分发到多个可用的服务实例中。Zuul可以与Spring Cloud中的Eureka服务发现和注册系统结合使用,从而可以找到运行在各个地方的服务的实例。通过使用Eureka,Zuul可以轻松地访问可用的服务,并使用负载均衡算法将请求分发到不同的实例中。 综上所述,Zuul是一种强大的服务网解决方案,可以通过动态路由、监控、弹性、安全性、负载均衡等功能提供API以及代理到底层服务。要配置Zuul,需要创建应用程序、配置路由、配置过滤器和配置负载均衡等。 ### 回答3: Spring Cloud Zuul是Spring Cloud服务架构中的一个API网服务,主要用于统一管理服务路由和访问控制。下面我将介绍如何进行Spring Cloud Zuul配置。 1. 添加依赖 在pom.xml中添加如下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> ``` 2. 配置Zuul路由规则 在配置文件中添加如下配置: ```yaml zuul: routes: app1: path: /app1/** serviceId: service1 app2: path: /app2/** serviceId: service2 ``` 上述配置表示,以`/app1`开头的请求将路由到名为`service1`的服务,以`/app2`开头的请求将路由到名为`service2`的服务。 3. 配置Zuul过滤器 在配置文件中添加如下配置: ```yaml zuul: routes: app1: path: /app1/** serviceId: service1 app2: path: /app2/** serviceId: service2 filters: pre-filter: pre: add-header-filter: name: X-Header-Param value: zuul-proxy ``` 上述配置表示,添加一个名为`pre-filter`的前置过滤器,在请求被路由之前将在请求头中添加一个名为X-Header-Param的参数,并赋值为`zuul-proxy`。 4. 集成Ribbon负载均衡 在配置文件中添加如下配置: ```yaml service1: ribbon: listOfServers: http://localhost:8081, http://localhost:8082 service2: ribbon: eureka: enabled: true ``` 上述配置表示,对于名为`service1`的服务,采用指定的服务器列表进行负载均衡;对于名为`service2`的服务,则启用Eureka进行负载均衡。 在完成以上配置之后,就可以通过Zuul来进行服务的路由和访问控制了,通过配置过滤器和负载均衡,可以对请求进行更加灵活和高效的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值