一 网关服务
1.1 Spring Cloud Zuul
Spring Cloud Zuul通过与Spring Cloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。这样的设计非常巧妙的将服务治理体系中维护的实例信息利用起来,使得将维护服务实例的工作交给了服务治理框架自动完成。而对应路由规则的维护,Zuul默认会将通过以服务名作为ContextPath的方式来创建路由映射。
其次,对于类似名为校验、登录校验在微服务架构中的冗余问题。理论上来说,这些校验逻辑在本质上与微服务应用自身的业务并没有多大的关系,所以它们完全可以独立成一个单独的服务存在,只是它们被剥离和独立出来后,并不是给各个服务调用,而是在API网关上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。Spring Cloud Zuul提供了一套过滤器机制,它可以很好的支持这样的任务。
1.2 快速入门
在实现各种API网关服务的高级功能之前,我们需要有一些后端服务可以用。这里我简单的使用两个工程:spring-cloud-user和spring-cloud-role两个服务,这两个服务提供访问能力。两个工程的代码如下:
springcloudrole.zip
springclouduser.zip
接下来我们使用网关路由到这两个服务上去
1.2.1 构建网关
- 创建一个基础的Spring Boot工程,命名为spring-cloud-gateway,并在pom.xml中引入spring-cloud-starter-zuul依赖,具体如下
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bianjf</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 引入alibaba-nacos Start -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入alibaba-nacos End -->
<!-- 引入alibaba-nacos-config Start -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 引入alibaba-nacos-config End -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot启动插件 Start -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Spring Boot启动插件 End -->
<!-- 编译工具 Start -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 编译工具 End -->
<!-- Maven Install 跳过Test阶段 Start -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<!-- Maven Install 跳过Test阶段 End -->
</plugins>
</build>
</project>
对于spring-cloud-starter-zuul依赖,该模块中不仅包含了Netflix Zuul的核心依赖zuul-core,它还包含了网关服务需要的重要依赖:
- spring-cloud-starter-hystrix
- spring-cloud-starter-ribbon
- spring-boot-starter-actuator
1.2.2 创建应用主类
创建应用主类,在主类上添加@EnableZuulProxy注解开启Zuul的API网关服务能力。
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication(scanBasePackages = {"com.bianjf"})
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
1.2.3 配置基础信息
在bootstrap.yml中配置Zuul的基础信息,如端口号,应用名,nacos地址等信息
server:
port: 8080
spring:
application:
#服务名称
name: spring-cloud-gateway
cloud:
nacos:
discovery:
#Nacos注册中心地址
server-addr: 192.168.10.3:8848
#固定本机的IP地址,防止注册时,本机使用其他网卡的地址与Nacos服务器进行通讯
ip: 192.168.10.79
config:
#Nacos配置中心的地址
server-addr: 192.168.10.3:8848
#Nacos配置文件的扩展名
file-extension: yml
#指定组
group: dev
inetutils:
#使用本地网卡
use-only-site-local-interfaces: true
1.2.4 请求路由
为了演示请求路由的功能,我们将上面的两个服务注册到服务中心(我这里使用的是nacos,eureka改变配置文件即可)
1.2.4.1 传统路由方式
在Spring Cloud Zuul实现路由功能非常简单,只需要对spring-cloud-gateway服务增加一些关于路由规则的配置,就能实现传统的路由转发功能,比如:
zuul:
routers:
user:
path: /user/**
url: http://localhost:8081
该配置定义了发往API网关服务的请求中,所有符合/user/**规则的访问都被路由转发到http://localhost:8081/ 地址上,也就是说,我们访问:http://localhost:8080/user/user/info 时,API网关服务会将该请求路由到:http://localhost:8081/user/info 接口上。其中配置zuul.routers.user.path中的user部分为路由的名称,可以任意定义。但是一组path和url映射关系的路由名要相同。
这种在实际中用的不多,实际环境中用的更多的是下面的这种
1.2.4.2 面向服务的路由
配置如下:
zuul:
#具体的路由规则
routes:
#用户服务
spring-cloud-user: /user/**
#角色服务
spring-cloud-role: /role/**
针对spring-cloud-user和spring-cloud-role两个微服务,在配置中使用的路由名称分别是spring-cloud-user和spring-cloud-role来映射,通过指定注册中心,除了自己注册成服务之外,同时也让Zuul能够活着spring-cloud-user和spring-cloud-role服务的实例清单,以实现path映射服务,再从服务中挑选实例来进行请求转发的完整路由机制。
通过上面的搭建工作,我们可以通过服务网关来访问spring-cloud-user和spring-cloud-role两个微服务了。
我的项目如下:
springcloudgateway.zip
1.3 请求过滤
Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截过滤,实现的方法很简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了。
/**
* 登陆过滤器
*
*/
public class LoginFilter extends ZuulFilter {
@Autowired
private JRedisDao jedisDao;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/**
* 是否需要被执行
*/
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();//request上下文对象
HttpServletRequest request = ctx.getRequest();//HttpServletRequest对象
String url = request.getRequestURI().toString();//请求地址
if (StringUtil.isNotNull(url) && this.isSkipCheck(url)) {//检查地址是否需要被执行
return false;
}
return true;
}
/**
* 执行具体逻辑
*/
@Override
public Object run() {
JSONObject resultJSON = new JSONObject();//返回数据对象
RequestContext ctx = RequestContext.getCurrentContext();//request上下文对象
HttpServletRequest request = ctx.getRequest();//HttpServletRequest对象
String accessToken = request.getParameter("access_token");//访问令牌
this.LOGGER.info("send {} request to {}" + request.getMethod() + "----- Request URL: " + request.getRequestURL().toString());
if (StringUtil.isNull(accessToken)) {//访问令牌为空
this.LOGGER.info("access_token is empty");
ctx.setSendZuulResponse(false);//false表示不向后端转发请求
ctx.setResponseStatusCode(200);
HttpServletResponse response = ctx.getResponse();//HttpServletResponse对象
response.setContentType("text/html;charset=utf-8");
ctx.setResponse(response);
resultJSON.put("errcode", 2);
resultJSON.put("errmsg", "访问令牌access_token不能为空");
ctx.setResponseBody(resultJSON.toJSONString());//返回数据
return null;
}
String loginName = this.jedisDao.get(CommonConstrant.PREFIX_TOKEN + "_" + accessToken);
if (StringUtil.isNull(loginName)) {
this.LOGGER.info("redis session is empty");
ctx.setSendZuulResponse(false);//false表示不向后端转发请求
ctx.setResponseStatusCode(200);
HttpServletResponse response = ctx.getResponse();//HttpServletResponse对象
response.setContentType("text/html;charset=utf-8");
ctx.setResponse(response);
resultJSON.put("errcode", 2);
resultJSON.put("errmsg", "登录Session过期, 请重新登录");
ctx.setResponseBody(resultJSON.toJSONString());//返回数据
return null;
}
this.LOGGER.info("access_token is ok");
return null;
}
/**
* 过滤器类型。表示在什么阶段该过滤器生效
* pre表示在请求路由之前被执行
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序
*/
@Override
public int filterOrder() {
return 2;// 优先级为0,数字越大,优先级越低
}
/**
* 跳过该方法定义的URL地址检查
* @param url
* @return
*/
private boolean isSkipCheck(String url){
String[] skipArr = {"login", "sendvirificationcode","checkversion","checkverificode","getmeetingdevids"};
for(String skip : skipArr){
if(url.toLowerCase().contains(skip)){
return true;
}
}
return false;
}
}
- filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
- pre:请求被路由之前执行
- route:请求路由时执行
- post:请求被路由之后执行
- error:发生错误时执行
- filterOrder:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来一次执行。数字越大,优先级越低
- shouldFilter:判断该过滤器是否需要被执行。
- run:过滤器的具体逻辑。
1.3 Cookie与头信息
默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HTTP请求头信息中的一些敏感信息,防止他们被传递到下游的服务器。默认的敏感头信息通过zuul.sensitiveHeaders参数定义,包括Cookie、Set-Cookit、Authorization三个属性。
通过设置全局参数为空来覆盖默认值:
zuul:
#清空默认的敏感的请求头key值,防止Cookie等key被拦截
sensitive-headers:
另外,Zuul对Ribbon和Feign同样支持。
评论区