统一网关Gateway
网关功能:身份认证和权限校验对工作人员或者是内部人员才能允许查看敏感信息一切请求先到网关再到微服务,这样就能做到身份认证和权限校验服务路由、负载均衡倘若我们通过了身份认证和权限校验,那怎么知道访问哪个微服务呢?服务路由:gateway网关不能处理对应的业务(比如用户查询功能),所以将请求转发到处理用户查询的服务(比如说userservice),因此网关必须根据请求判断把请求放到哪个服务里面负载均衡
一、网关Gateway作用介绍
网关功能:
身份认证和权限校验
对工作人员或者是内部人员才能允许查看敏感信息
一切请求先到网关再到微服务,这样就能做到身份认证和权限校验
服务路由、负载均衡
倘若我们通过了身份认证和权限校验,那怎么知道访问哪个微服务呢?
服务路由: gateway网关不能处理对应的业务(比如用户查询功能),所以将请求转发到处理用户查询的服务(比如说userservice),因此网关必须根据请求判断把请求放到哪个服务里面
负载均衡:同样是userservice,但是可能有多个实例,此时就要根据负载均衡的规则来判断向哪个服务发送请求(从多个实例里面挑一个)
请求限流
对微服务的一种保护措施
限制人进入的流量,能够允许用户请求的量
比如我们限制500次用户请求,当超过500次之后就会限制进入,要不等着要不回去等人少的时候再来
1.1 网关技术的两种实现
在SpringCloud中网关的实现包括两种:
gateway
zuul
Zuul是基于Servlet的实现,属于阻塞式编程。
而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
网络编程中有两种模式:阻塞和非阻塞,默认是采用阻塞方式。
阻塞和非阻塞是对操作请求者在等待返回结果时的状态描述:
阻塞时,在操作请求结果返回前,当前线程会被挂起,得到结果之后返回;
**非阻塞时,**如果不能立刻得到结果,系统不会挂起当前线程,而是直接返回错误信息,因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。
**本质:**阻塞和非阻塞本质上是本地系统对socket的不同处理方式,并不影响socket链接,也不会影响通信对方,通信双方可以自由选择阻塞还是非阻塞,例如:客户端设置成阻塞,服务器端accept 后可以设置成非阻塞都是可以的。
非阻塞是阻塞方式的改进方案,在大部分情况下可以获得更好的性能。
二、Gateway网关快速入门
2.1 搭建网关的步骤
2.1.1 创建新module并引入依赖
创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖 新的module(Gateway)也是一个微服务,也需要服务注册或者从nacos拉取服务-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
上面就是网关的搭建成功了
2.1.2 编写路由配置及nacos地址
server:
port: 10010 # 网关端口,这个随便自己配置
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
# 上面的是nacos的服务注册与发现
# 下面的才是网络的路由配置
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
解析:
**uri: http://127.0.0.1:8081 ** 这是向一个具体的ip和端口进行路由,相当于把路由直接写死了,将来都往这个自己去发,我们一般不使用这种方式
uri: lb://userservice lb就是负载均衡(loadBalance,代表路由的时候要做负载均衡),后面userservice是服务名
predicates 路由断言,判断请求是否符合路由规则的条件
什么是断言?
编程术语,表示为一些布尔表达式
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
如果我还想配置一个order-service的工程呢?
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
2.1.3 访问Gateway
我们的gateway项目中只有一个pom.xml配置文件一个启动类,并没有做任何的处理逻辑,但是却能访问到userservice中的数据
2.2 流程图
2.3 总结
网关搭建步骤:
创建项目,引入nacos服务发现和gateway依赖
配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
路由id:路由的唯一标示
路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
路由断言(predicates):判断路由的规则
路由过滤器(filters):对请求或响应做处理
三、路由断言工厂Route Predicate Factory
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
像这样的断言工厂在SpringCloudGateway还有十几个
名称 | 说明 | 示例 |
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.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** (多个路径规则可以用逗号隔开,{segment}表示占位符,还能获取路径中的这个参数信息,大多数情况下并不需要获取路径中的参数信息) |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24(对请求者的ip地址做限制) |
Weight | 权重处理 |
3.1 示例:
当请求在2037-01-20T17:42:47.789-07:00[Asia/Shanghai]之后才能成功的访问到Path=/order/**
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
- After=2037-01-20T17:42:47.789-07:00[Asia/Shanghai]
很显然现在的时间并不符合这个配置,那如果不符合的话,会有什么情况?
不符合这套配置的话会直接跳过这个,不管他,等时间到了之后再进行。如果全部都找完都没有符合的,那么就认为这个请求无法路由。
无法路由会怎么样?
会认为404了。如下图所示,没有符合规则的路由导致找不到正确的页面
四、 过滤器 GatewayFilter
GatewayFilter是网关中的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理(不仅仅是请求,也有响应)
Spring提供了31种不同的路由过滤器工厂。例如:
名称 | 说明 |
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
... |
4.1 给所有进入userservice的请求添加一个请求头
给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
这个过滤器的形式是一个数组的形式,但是数组中的每一条可以配置key,value
key=Truth
value=itcast is freaking awesome!
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
filters: # 过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
验证:
修改一下userservice中controller层的代码,可以获取到请求头并输出在控制台
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth",required = false) String truth) {
System.out.println(truth);
return userService.queryById(id);
}
我们发现控制台也输出了我们自定义的请求头的内容了
4.2 默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
default-filters 默认过滤器,会对所有的路由请求都生效
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters: # 默认过滤器,会对所有的路由请求都生效
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
五、全局过滤器 GlobalFilter
5.1 对比GatwayFilter过滤器
**作用:**处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
**区别:**GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现,定义方式是实现GlobalFilter接口。
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
这个接口中只有
一个方法:filter
**两个参数:**exchange、chain
**exchange:**代表请求上下文,这个上下文表示从请求进入网关开始到结束为止,整个流程中都可以共享exchange对象,这个对象可以拿到请求相关的信息、响应相关的信息,甚至可以向里面存东西取东西
**作用:**让我们编写整个过滤器的业务逻辑的,我们需要的信息,里面都有
**chain:**过滤器链,这个过滤器链就是除了这个过滤器之外还有其他的过滤器,他的作用就是放行。调用过滤器链往后走,那么就等于你这里逻辑处理完了交给别人处理了。
**作用:**放行作用,交给下一个过滤器去处理好
返回值Mono
5.2 定义全局过滤器拦截并判断用户身份
**需求:**定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
参数中是否有authorization,
authorization参数值是否为admin
如果同时满足则放行,否则拦截
代码实现
@Component //注入spring作为一个bean
@Order(-1) // 顺序值,默认是2147483647,值越小,优先级越高除了这种方法之外我们可以继承Ordered并实现方法,修改方法的返回值即可
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数 (ServerHttpRequest 和servlet中的不是一个)
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2.获取参数中的authorization参数
// 我们一般从里面取出第一个匹配的
String auth = params.getFirst("authorization");
// 3.判断参数是否等于admin
if ("admin".equals(auth)) {
// 4.是,放行
return chain.filter(exchange);
}
// 5.否,拦截
// setComplete直接结束,所以是拦截了,但是这样的拦截用户体验不好,看不到结果
// return exchange.getResponse().setComplete();
// 为了解决这个情况我们可以设置状态码 401代表未登录,但是这里不能直接传入状态码
// 在这个地方需要一个枚举类型,HttpStatus.UNAUTHORIZED表示未登录、未认证的意思
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
成功的场景
失败的场景
5.3 总结
全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤?
实现GlobalFilter接口、添加@Order注解或实现Ordered接口、编写处理逻辑
六、过滤器的执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后(只有在路由确认后才知道哪些过滤器生效,不同的路由有不同的过滤器),会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
下图是局部的过滤器和全局的过滤器,区别就是一个放在路由内,一个放在默认的过滤器中
从本质来看这两个是一个样子的,底层都是AddRequestHeaderGatewayFilterFactory,这个过滤器的工厂就会读取配置文件最后生成一个真正的过滤器,生成的真诚过滤器的名字叫做gatewayFilter,所以我们认为 路由过滤器和default过滤器是同一类,都叫gateway filter
、
在网关当中,所有的global filter都可以被适配成gateway filter,这样以来网络中所有的过滤器都是gateway filter类型
6.1 过滤器的执行顺序
既然所有的过滤器都是同一种类型,那我们就可以把这些过滤器扔到一个集合中去做排序了
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter(自定义过滤器)的顺序执行。
七、网关的cors跨域配置
**跨域:**域名不一样就叫做跨域
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080和localhost:8081
**跨域问题:**浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题(连两个关键点,浏览器与ajax)
可能会想,我们之前8080向8081发起请求的时候,为什么没有跨域问题呢?
我们order-service到user-service和浏览器没有任何的关系,也不是ajax,所以不会有跨域问题
解决方案:CORS
spring:
cloud:
gateway:
# 。。。复制的时候光复制下面的就行
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(询问请求,网关中特有的,默认情况下options请求是被拦截的因为有跨域问题,但是加上这个之后就不会拦截options请求)
corsConfigurations:
'[/**]': # 拦截哪些请求,/**表示拦截一切请求
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息,允不允许某个请求头跨域
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期,在这段时间内浏览器不再发起询问而是直接放行
更多推荐
所有评论(0)