作者:koglum | 来源:互联网 | 2023-09-15 04:33
github:https:github.comMaple521springcloudtreemasterapi-gateway-%E8%AF%B7%E6%B1%82%
github:https://github.com/Maple521/springcloud/tree/master/api-gateway-%E8%AF%B7%E6%B1%82%E8%BF%87%E6%BB%A4
在实现了请求路由功能之后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。但是,每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开放。然而, 目前的服务路由并没有限制权限这样的功能,所有请求都会被毫无保留地转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。不过,这样的做法并不可取,它会增加日后系统的维护难度,因为同一个系统中的各种校验逻辑很多情况下都是大致相同或类似的,这样的实现方式会是的相似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是我们不希望看到的。所以,比较好的做法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。在完成了剥离之后,有不少开发者会直接在微服务应用中通过调用鉴权服务来实现校验,但是这样的做法仅仅只是解决了鉴权逻辑的分离,并没有在本质上将这部分属于冗余的逻辑从原有的微服务应用中拆分出,冗余的拦截器或过滤器依然存在。
对于这样的问题,更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统已经有了统一入口,既然这些校验与其他业务无关,那何不在请求到达的时候就完成校验和过滤,微服务杨祐宁高端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用接口的开发和测试复杂度也得到了相应的降低。
为了在API网关中实现对客户端请求的校验,我们将继续介绍Spring Cloud Zuul的另外一个核心功能:请求过滤。Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截和过滤,实现的方法非常简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了。
下面的代码定义了一个简单的Zuul过滤器,它实现了在请求被路由之前检查HttpServletRequest中是否有accessToken参数,若有就进行路由,若没有就拒绝访问返回401 Unauthorizes错误。
package com.api.gateway.filter;import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;import javax.servlet.http.HttpServletRequest;public class AccessFilter extends ZuulFilter {private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();logger.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());String accessToken = request.getParameter("accessToken");if (StringUtils.isBlank(accessToken)) {logger.warn("access token is empty");ctx.setSendZuulResponse(false);ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());}logger.info("access token ok");return null;}
}
在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写下面4个方法来实现自定义的过滤器。这4个方法分别定义了如下内容。
- filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
- filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
- shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来制定过滤器的有效范围。
- run:过滤器的具体逻辑。这里我们通过 ctx.setSendZuulResponse(false) 令 Zuul 过滤该请求,不对其进行路由,然后通过 ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()) 设置了其返回的错误码,当然也可以进一步优化我们的返回,比如,通过 ctx.setResponseBody(body) 对返回的body内容进行编辑等。
在实现了自动以过滤器之后,它并不会直接生效,我们还需要为其创建具体的Bean才能启动该过滤器,比如,在应用主类中增加如下内容:
package com.api.gateway;import com.api.gateway.filter.AccessFilter;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayApplication {public static void main(String[] args) {new SpringApplicationBuilder(ApiGatewayApplication.class).web(true).run(args);}@Beanpublic AccessFilter accessFilter() {return new AccessFilter();}
}
在对api-gateway服务完成了上面的改造之后,我们可以重新启动它,并发起下面的请求,对上面定义的过滤器做一个验证。
- http://localhost:5555/api-a/hello:返回401错误
- http://localhost:5555/api-a/hello?accessToken=token:正确路由到hello-service的 /hello 接口,并返回了Hello World。
到这里,对于API网关服务的快速入门示例就完成了。通过对Spring Cloud Zuul 两个核心功能的介绍,相信读者已经能够体会到API网关服务队微服务架构的重要性了,就目前掌握的API网关知识,我们可以将具体原因总结如下:
- 它作为系统的统一入口,屏蔽了系统内部各个微服务的细节
- 它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。
- 他可以实现接口权限校验与微服务业务逻辑的解耦。
- 通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
实际上,基于Spring Cloud Zuul 实现的API网关服务除了上面所示的优点之外,它还有一些更加强大的功能,我们将在后续的章节对其进行更深入的介绍。通过本节的内容,我们只是希望以一个简单的例子先来简单的认识一下API网关服务提供而基础功能以及它在微服务架构中的重要地位。