为什么需要微服务网关
不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才 能完成一个业务需求。比如一个电影购票的收集APP,可能回调用电影分类微服务,用户 微服务,支付微服务等。如果客户端直接和微服务进行通信,会存在一下问题:
- 客户端会多次请求不同微服务,增加客户端的复杂性
- 存在跨域请求,在一定场景下处理相对复杂
- 认证复杂,每一个服务都需要独立认证
- 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通 信,那么重构会难以实施
- 某些微服务可能使用了其他协议,直接访问有一定困难
上述问题,都可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中 间层,所有的外部请求都会先经过微服务网关。
什么是Zuul
Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使 用。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
- 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
- 动态路由:动态将请求路由到不同后端集群
- 压力测试:逐渐增加指向集群的流量,以了解性能
- 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
- 静态响应处理:边缘位置进行响应,避免转发到内部集群
- 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使 用多样化
Spring Cloud对Zuul进行了整合和增强。
使用Zuul后,架构图演变为以下形式
Zuul路由转发
管理后台微服务网关
(1)创建子模块javakf_zuul_admin,pom.xml引入eureka-client 和zuul的依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
(2)创建application.yml
server: port: 10003 spring: application: name: javakf-zuul-admin eureka: client: service-url: defaultZone: http://localhost:8888/eureka zuul: routes: javakf-user: path: /user/** service-id: javakf-user javakf-sms: path: /sms/** service-id: javakf-sms
(3)编写启动类
@SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulAdminApplication { public static void main(String[] args) { SpringApplication.run(ZuulAdminApplication.class); } }
网站前台的微服务网关
(1)创建子模块javakf_zuul_web,pom.xml引入依赖zuul
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies>
(2)创建application.yml
server: port: 10004 spring: application: name: javakf-zuul-web eureka: client: service-url: defaultZone: http://localhost:8888/eureka zuul: routes: javakf-user: path: /user/** service-id: javakf-user javakf-sms: path: /sms/** service-id: javakf-sms
(3)编写启动类
@SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulWebApplication { public static void main(String[] args) { SpringApplication.run(ZuulWebApplication.class); } }
Zuul过滤器
Zuul过滤器快速体验
在javakf_zuul_web创建一个简单的zuul过滤器
@Component
public class WebFilter extends ZuulFilter {
@Override
public String filterType() {// 过滤器类型
return "pre";// 前置过滤器
}
@Override
public int filterOrder() {// 过滤器的执行顺序,数字越大,越靠后执行
return 0;
}
@Override
public boolean shouldFilter() { // 过滤器的开关
return true;// 表示开启
}
@Override
public Object run() throws ZuulException {// 过滤器的执行逻辑
System.out.println("经过了zuul过滤器");
return null;
}
}
filterType
:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过 滤器类型,具体如下:
- pre :可以在请求被路由之前调用
- route :在路由请求时候被调用
- post :在route和error过滤器之后被调用
- error :处理请求时发生错误时被调用
filterOrder
:通过int值来定义过滤器的执行顺序
shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可 实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效
run
:过滤器的具体逻辑。
测试:
http://localhost:10001/user/sms/1 (直接调用)
http://localhost:10004/user/user/sms/1 (通过路由转发)会发现过滤器已经执行
网站前台的token转发
现在在过滤器中接收header,转发给微服务
修改javakf_zuul_web的过滤器。如果有token,直接转发。
@Override
public Object run() throws ZuulException {// 过滤器的执行逻辑
System.out.println("经过了zuul过滤器");
// 向header中添加鉴权令牌
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String authHeader = request.getHeader("Authorization");// 获取头信息
System.out.println("Authorization:" + authHeader);
if (authHeader != null) {
requestContext.addZuulRequestHeader("Authorization", authHeader);
}
return null;
}
管理后台过滤器实现token校验
修改javakf_zuul_admin的过滤器, 因为是管理后台使用,所以需要在过滤器中对token 进行验证。
(1)javakf_zuul_admin引入javakf_common依赖 ,因为需要用到其中的JWT工 具类
<dependency> <groupId>cn.com.javakf</groupId> <artifactId>javakf_common</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
(2)修改javakf_zuul_admin配置文件application.yml
jwt: config: key: javakf
(3)修改javakf_zuul_admin的启动类,添加bean
@Bean public JwtUtil jwtUtil() { return new JwtUtil(); }
(4)javakf_zuul_admin编写过滤器类
@Component public class AdminFilter extends ZuulFilter { @Autowired private JwtUtil jwtUtil; @Override public String filterType() {// 过滤器类型 return "pre";// 前置过滤器 } @Override public int filterOrder() {// 过滤器的执行顺序,数字越大,越靠后执行 return 0; } @Override public boolean shouldFilter() { // 过滤器的开关 return true;// 表示开启 } @Override public Object run() throws ZuulException {// 过滤器的执行逻辑 System.out.println("经过了zuul过滤器"); RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); // 判断是否是登陆的地址 String url = request.getRequestURL().toString(); System.out.println("url:" + url); if (url.indexOf("/admin/login") > 0) { // 登陆地址直接转发 System.out.println("登陆地址直接转发" + url); return null; } String authHeader = request.getHeader("Authorization");// 获取头信息 System.out.println("Authorization:" + authHeader); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); Claims claims = jwtUtil.parseJWT(token); if (claims != null) { if ("admin".equals(claims.get("roles"))) { requestContext.addZuulRequestHeader("Authorization", authHeader); return null; } } } // 不合法请求? System.out.println("终止转发...."); requestContext.setSendZuulResponse(false);// false表示不转发 requestContext.setResponseStatusCode(401);// http状态码 requestContext.setResponseBody("无权访问"); requestContext.getResponse().setContentType("text/html;charset=UTF-8"); return null; } }
注意:这里通过 ctx.setSendZuulResponse(false) 令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401) 设置了其返回的错误码