SpringCloud-网关
一、网关是什么?
微服务的网关是一个充当服务的入口和出口的服务器,它接收客户端的请求并将其路由到适当的微服务实例。网关还可以处理一些共享的功能,如认证、授权、负载均衡、日志记录和监控等。通过使用网关,可以简化微服务架构中的通信和管理,提高系统的可靠性和安全性。
二、什么是Spring Cloud Gateway?
Spring Cloud Gateway是Spring基于Spring5.0、SpringBoot2.0、Project Reactor等技术开发的网关技术
-
旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
-
它不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控和限流。
-
它是用于代替NetFlix Zuul的一套解决方案:webflux
Spring Cloud Gateway组件的核心是一系列的过滤器,通过过滤器可以将客户端的请求转发到应用的微服务(这个过程叫路由)。Spring Cloud Gateway是站在整个微服务最前沿的防火墙和代理器,隐藏微服务节点的ip信息、从而加强安全保护。
Spring Cloud Gateway本身也是一个微服务,需要注册到注册中心
Spring Cloud Gatewa的核心功能是:路由和过滤
三、网关入门
1. 核心概念
路由Route
一个路由的配置信息,由一个id、一个目的地url、一组断言工厂、一组过滤器组成。
断言Predicate
断言是一种判断规则;如果客户端的请求符合要求的规则,则这次请求将会被路由送到目的地
Spring Cloud Gateway的断言函数输入类型是Spring5.0框架中的ServerWebExchange,它允许开发人员自定义匹配来自HTTP请求中任何信息
过滤器Filter
Spring Cloud Gateway中的Filter可以对请求和响应进行过滤修改。是一个标准的Spring WebFilter。它分为两类:
- Gateway Filter:局部过滤器(路由过滤器),应用于单个路由或者一组路由,通常由SpringCloudGateway内置好
- Global Filter:全局过滤器,应用于所有路由
2. 使用步骤
- 创建一个模块:网关模块,导入gateway的依赖
- 创建引导类:开启服务注册@EnableDiscoveryClient
- 创建配置文件:
- 网关的端口
- 设置服务名称
- 设置注册中心的地址
- 配置网关的路由:给每个微服务设置路由信息
3. 示例
1.创建一个模块app-gateway,导入依赖
<dependencies>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos-discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
2.创建引导类,开启注册服务
@EnableDiscoveryClient//开启注册服务
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.编写配置文件
server:
port: 10000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 #注册中心地址
gateway:
routes: #路由配置,是个数组
- id: user-service # 路由id
uri: lb://user-service #路由目的地的地址: lb 表示从注册中心拉取服务列表,并启用负载均衡
predicates: #断言,什么样的请求可以到达目标地
- Path=/user/**
4.测试
四、Gateway断言
我们在配置文件中写的predicates
断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个,而我们需要掌握的只有Path
所有断言工厂的使用方式都是 在网关的路由配置中,使用predicates
配置的:
spring:
cloud:
gateway:
routes:
- id: 路由唯一标识
uri: lb://user-service #路由目的地的地址
predicates: #断言,可以配置多个
- Path=/user/** # - 断言名称=配置值
其它断言工厂参考:docs.spring.io/spring-clou…
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
五、Gateway过滤器
1. 网关登录校验
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。
登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。如果每个微服务都去做登录校验,这就存在着两大问题:
- 每个微服务都需要知道JWT的秘钥,不安全
- 每个微服务重复编写登录校验代码、权限校验代码,麻烦
既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做,这样之前说的问题就解决了:
- 只需要在网关和用户服务保存秘钥
- 只需要在网关开发登录校验功能
不过,这里存在几个问题:
- 网关路由是配置的,请求转发是Gateway内部代码,我们如何在转发之前做登录校验?
- 网关校验JWT之后,如何将用户信息传递给微服务?
- 微服务之间也会相互调用,这种调用不经过网关,又该如何传递用户信息?
2. 网关过滤器
登录校验必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway
内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway
内部工作的基本原理。
如图所示:
- 客户端请求进入网关后由
HandlerMapping
对请求做判断,找到与当前请求匹配的路由规则(Route
),然后将请求交给WebHandler
去处理。 WebHandler
则会加载当前路由下需要执行的过滤器链(Filter chain
),然后按照顺序逐一执行过滤器(后面称为**Filter
**)。- 图中
Filter
被虚线分为左右两部分,是因为Filter
内部的逻辑分为pre
和post
两部分,分别会在请求路由到微服务之前和之后被执行。 - 只有所有
Filter
的pre
逻辑都依次顺序执行通过后,请求才会被路由到微服务。 - 微服务返回结果后,再倒序执行
Filter
的post
逻辑。 - 最终把响应结果返回。
如图中所示,最终请求转发是有一个名为NettyRoutingFilter
的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到**NettyRoutingFilter
**之前,这就符合我们的需求了!
那么,该如何实现一个网关过滤器呢?
网关过滤器链中的过滤器有两种:
GatewayFilter
:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
.GlobalFilter
:全局过滤器,作用范围是所有路由,不可配置。
3. GatewayFilter
应用到单个路由上,是局部过滤器,必须要配置到配置文件
它需要实现GatewayFilterFactory接口,并且需要在配置文件中配置才会生效;
GatewayFilter也可以配置为默认过滤器,针对所有路由进行过滤
所有的过滤器都可以参考官方手册 docs.spring.io/spring-clou…
局部过滤器
spring:
cloud:
gateway:
default-filters:
- 过滤器名称=配置的参数值
- 过滤器名称=配置的参数值
routes:
- id: 路由id
uri: 路由目的地lb://目标服务名
predicates:
- Path=/xxx/**
filters:
- 过滤器名称=配置的参数值
- 过滤器名称=配置的参数值
配置过滤器示例
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service #用户服务的路由配置
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- AddResponseHeader=abc, user service is strong
- id: order-service #订单服务的路由配置
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- AddResponseHeader=aaa, order service works great
default-filters: #添加到这里的过滤器,对所有路由都生效
- AddResponseHeader=company, itcast
4. GlobalFilter
应用到所有的路由上,是全局过滤器,不需要配置到配置文件
它不需要在配置文件中配置,只要实现GlobalFilter接口即可
自定义全局过滤器
使用步骤
- 创建一个类,类上加@Component
- 实现GlobalFilter接口,重写filter方法,在filter方法里实现过滤逻辑
- 实现Ordered接口,重写getOrder方法,如果需要设置过滤器执行顺序的话
示例
@Component
public class DemoGlobalFilter implements GlobalFilter , Ordered {
//执行过滤的方法
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//如果要处理请求,就从exchange里获取request对象
ServerHttpRequest request = exchange.getRequest();
// 获取请求路径
System.out.println("本次请求路径:" + request.getURI());
// 获取请求头
System.out.println("请求头Host:" + request.getHeaders().getFirst("Host"));
//如果要处理响应,就从exchange里获取response对象
ServerHttpResponse response = exchange.getResponse();
// 设置响应状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置响应cookie
response.addCookie(ResponseCookie.from("cookieName", "cookieValue").build());
// 结束本次请求,并返回响应
// return response.setComplete();
//放行
return chain.filter(exchange);
}
//设置过滤器的执行顺序,值越小,执行的越早
@Override
public int getOrder() {
return 0;
}
}
六、跨域问题
1. 浏览器的同源策略
1.1 什么是同源策略
1995年,同源策略由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个同源策略。它是指:一个页面,只允许访问与页面同源的某些资源。
-
所谓的同源包含:同协议、同域名(同IP)、同端口。假如有一个资源是
http://www.itcast.cn/a.html
,那么:https://www.itcast.cn/user/1
:不同源,因为协议不同http://itcast.cn/user/1
:不同源,因为域名不同http://www.itcast.cn:81/user/1
:不同源,因为端口不同http://www.itcast.cn/user/1
:同源,因为同协议、同域名、同端口 -
被同源限制的资源有:
- Cookie、LocalStorage 和 IndexDB:只能同源的页面进行访问
- DOM和js对象 :只能同源的页面才能获得
- AJAX :只能向同源的资源发Ajax请求
1.2 什么是跨域问题
如果http://localhost:80/index.html
页面上要发起一个Ajax请求,请求的目的地是:http://localhost:8080/user/1
,这就是一个跨域Ajax请求了。
受限于浏览器的同源策略,这次请求是必定发送不成功的
但是目前流行的开发方式是前后端分离,即前端资源使用nginx部署到单独的服务器上,服务端项目部署到其它服务器上,这样的情况下,跨域请求就不可避免了。我们该如何规避浏览器的同源策略,允许浏览器跨域发送Ajax请求呢?
2. 解决跨域问题
只需要在网关里添加如下配置即可:
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: "*" # 允许哪些网站的跨域请求。 *表示任意网站
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
转载自:https://juejin.cn/post/7362905254726664246