不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才 能完成一个业务需求。比如一个电影购票的收集APP,可能回调用电影分类微服务,用户 微服务,支付微服务等。如果客户端直接和微服务进行通信,会存在一下问题:
上述问题,都可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中 间层,所有的外部请求都会先经过微服务网关。
Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使 用。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
Spring Cloud对Zuul进行了整合和增强。
使用Zuul后,架构图演变为以下形式
(1)创建子模块javakf_zuul_admin,pom.xml引入eureka-client 和zuul的依赖
org.springframework.cloud</groupId> spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> org.springframework.cloud</groupId> 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
org.springframework.cloud</groupId> spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> org.springframework.cloud</groupId> 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); } }
在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中定义了四种不同生命周期的过 滤器类型,具体如下:
filterOrder
:通过int值来定义过滤器的执行顺序
shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可 实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效
run
:过滤器的具体逻辑。
测试:
http://localhost:10001/user/sms/1 (直接调用)
http://localhost:10004/user/user/sms/1 (通过路由转发)会发现过滤器已经执行
现在在过滤器中接收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;
}
修改javakf_zuul_admin的过滤器, 因为是管理后台使用,所以需要在过滤器中对token 进行验证。
(1)javakf_zuul_admin引入javakf_common依赖 ,因为需要用到其中的JWT工 具类
cn.com.javakf</groupId> javakf_common</artifactId> 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) 设置了其返回的错误码