Zuul API Gateway
版本采用 Zuul Core 1.3.0
微服务首先要解决的问题就是随着服务数量的增加而导致的部署授权、负载均衡、通信管理、分析和改变的难度增加的问题,面对这个问题,主流的解决方案就是增加一个API GATEWAY,由API GATEWAY提供访问限制、安全、流量控制、分析监控、日志、请求转发、合成和协议转换功能,以解放放开发者去把精力集中在具体逻辑的代码,而不是把时间花费在考虑如何解决应用和其他微服务链接的问题上。
当前API GATEWAY的框架众多,Netflix开源的高性能高可用的API网关特点突出。其本身是一个服务,作为SpringCloud的一部分,能注册到Eureka的注册中心中而被发现。然而Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了包含验证、见解、压力测试、金丝雀测试、动态路由、服务迁移、减载、安全、静态响应处理、主动流量管理等功能。其定义了四种标准的过滤器类型,除此之外,Zuul还可以自定义过滤器,以适应灵活的使用场景。
内容概览
快速部署
Spring Cloud中,包括API网关以及服务注册中心都是以服务的形式存在,所以,要部署Zuul
,就是构建一个服务,在服务中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.3.0</version>
</dependency>
在启动类上加入@EnableZuulProxy
开启ZuulAPI网关
框架说明-业务
单点登录,顾名思义,就是提供基于身份认证的单点登录服务。服务提供两个端口:
- login:用户登录,包含用户注册,面向用户
- identify:身份认证,面向其他服务,为其他服务认证用户信息提供的接口
对比以上两个端口,我们可以相对清楚的理解Zuul及其插件的效果和配置方法。
框架说明-组件
本例使用Zuul代理,并添加了eureka注册中心服务,oauth2协议等实现反向代理和单点登录。
组件架构如下图所示:
- login端口供客户端获得用户的凭证
- identify端口供其他服务查证用户的合法性,用户登录后,获得凭证访问其他服务,其他服务根据用户的凭证通过identify端口验证用户的合法性。
Zuul基本使用
创建路由映射
zuul默认会将通过以服务名作为contextPath的方式来创建路由映射,本例使用默认方式来创建路由映射,如果有一些特殊的情况,还可以做一些特别的配置
spring cloud zuul实现路由转发只需要增加一些关于路由规则的配置,就能实现:
zuul.routes.api-a-url.path=/apiXXX/**
zuul.routes.api-a-url.url=http://localhost:8080
如果将zuul和eureka整合使用,只需要添加相应的路由规则,则可以将路由的path直接映射到某个具体的服务:
spring.application.name=api-gateway
server.port=5555
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=feign-consumer
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
Zuul标准过滤器
Zuul中定义了四种标准的过滤器类型,这些过滤器类型对应于请求的典型生命周期。
PRE
过滤器: 在请求被路由之前调用, 可用来实现身份验证、在集群中选择请求的微服务、记录调试信息等;ROUTING
过滤器: 在路由请求时候被调用;POST
过滤器: 在路由到微服务以后执行, 可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等;ERROR
过滤器: 在处理请求过程时发生错误时被调用。
Zuul过滤器的类型其实也是Zuul过滤器的生命周期,其执行过程如下:
使用如下:
public class MyFilterPre extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
System.out.println("pre");
return null;
}
}
其中,filterType()
对应于上文提到的四种标准的过滤器类型,filterOrder()
则规定了过滤器的的执行顺序,序号越小越早执行。
Zuul自定义过滤器
自定义过滤器只需要继承ZuulFilter
,并重写四个方法即可。
单点登录实现
-
添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>1.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
它会自动包含spring-cloud-starter-security依赖。
-
配置认证服务,并注册到eureka,其中,为了方便多个client连接,需要进行如下配置:
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("first").secret("passwordforauthserver") .redirectUris("http://localhost:8080/").authorizedGrantTypes("authorization_code", "refresh_token") .scopes("myscope").autoApprove(true).accessTokenValiditySeconds(30).refreshTokenValiditySeconds(1800) .and() .withClient("second").secret("passwordforauthserver") .redirectUris("http://localhost:8081/").authorizedGrantTypes("authorization_code", "refresh_token") .scopes("myscope").autoApprove(true).accessTokenValiditySeconds(30).refreshTokenValiditySeconds(1800); }
-
提供REST服务接口供客户端调用
@RestController public class PersonInfoController { @GetMapping("/person") @PreAuthorize("hasAnyRole('ADMIN', 'USER')") public @ResponseBody Person personInfo() { return new Person("boss", "FuZhou", "China", 17); } }
-
提供一个/usr接口供客户端来获得用户的凭证
@RestController public class ResourceController { @RequestMapping("/user") public Principal user(Principal user) { return user; } }
-
客户端应用需要在配置类中增加安全注解
@Configuration @EnableOAuth2Sso public class SiteSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // ... } }
-
对于需要身份验证的请求,将被重定向到授权服务器:
security: oauth2: client: accessTokenUri: http://localhost:7070/authserver/oauth/token userAuthorizationUri: http://localhost:7070/authserver/oauth/authorize clientId: first clientSecret: passwordforauthserver resource: userInfoUri: http://localhost:9000/user
-
上下文将访问令牌转发给所请求的服务,并且如果令牌到期了也将会刷新令牌,为此需要实现令牌传递
@Bean public OAuth2RestOperations restOperations( OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) { return new OAuth2RestTemplate(resource, context); }
-
为了使任何访问客户端应用/api端点的请求被重定向到资源服务的URL。并且这些请求都会带着OAuth令牌,可以通过配置zuul网关实现:
zuul: routes: resource: path: /api/** url: http://localhost:9000 user: path: /user/** url: http://localhost:9000/user
此时,配置类中需要增加注解
@EnableZuulProxy
开启代理
生产环境
TODO
常见问题
TODO
更新计划
- 增加流量监控实现
- 增加负载均衡实现
- 增加服务监测实现