目录:
一、统一网关Gateway
1. 为什么需要网关
2. 网关的功能
1. 身份认证和权限校验
2. 服务路由、负载均衡
3. 请求限流
3. 网关的技术实现
4. 总结网关的作用
5. gateway快速入门
6. 搭建网关服务的步骤
一、统一网关Gateway
1. 为什么需要网关
在SpringCloud中,微服务直接摆在那里允许任何人都可以访问,不太安全。因此需要进行身份验证,一切请求先到网关Gateway再到微服务,验证过后再进行放行。但是放行过后,问题又来了,当用户放松请求处理业务时,网关肯定处理不了业务,需要把请求给对应的微服务;但是需要判断是发给order-service还是user-service进行处理?每一个微服务后面肯定有很多实例,所以还需要进行服务路由和负载均衡!允许用户的请求量限量是对微服务的一种保护机制。
2. 网关的功能
2.1 对用户请求做身份认证、权限校验;
2.2 将用户请求路由到微服务,并实现负载均衡;
2.3 对用户请求做限流。
3. 网关的技术实现
在SpringCloud中网关的实现包括两种:1Gateway;2Zuul。SpringCloudGateway是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。Zuul是基于Servlet的实现,属于阻塞式编程。
4. 总结网关的作用
对用户请求做身份认证、权限校验;将用户请求路由到微服务,并实现负载均衡;对用户请求做限流。
5. gateway快速入门
搭建网关服务的步骤:第一步:创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖。这里需要Nacos依赖是因为也要把网关Gateway也注入注册中心Nacos里!
第一步:添加网关依赖```xml
```
第二步:服务的启动需要启动类
```java
package cn.itcast.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
```
第三步:application.yml中编写路由规则配置及nacos地址
网关搭建步骤
1. 创建项目,引入nacos服务发现和gateway依赖;
2. 配置application.yml,包括服务基本信息、nacos地址、路由;
3. 断言工厂
以下是重构后的代码:
```yaml
server:
port: 10010 # 服务端口
spring:
application:
name: gateway # 服务名称
cloud: nacos:
server-addr: localhost:8848 # nacos服务地址
gateway: # 服务路由配置
routes: # 表示规则
- id: userservice # 路由标识
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates:
- Path=/user/** # 路径断言,判断是否以/user开头
- id: orderservice
uri: lb://order-service
predicates:
- Path=/order/**
```
网关路由可以配置的内容包括:
1. 路由ID:路由的唯一标识符;
2. URI:路由的目标地址,支持负载均衡(Load Balancer,简称LB)和HTTP两种;
3. Predicates:路由断言,用于判断请求是否符合要求。如果符合要求,则将请求转发到路由目标地址;
4. Filters:路由过滤器,用于处理请求或响应。
路由断言工厂Route Predicate Factory负责解析配置文件中的断言规则字符串,并将其转换为路由判断条件。例如,路径匹配规则“Path=/user/**”是通过org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。在Spring Cloud Gateway中,还有许多其他类似的断言工厂。
Spring提供了11种基本的Predicate工厂,如下所示:
| 名称 | 说明 | 示例 |
| --- | --- | --- |
| 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, 192.168.2.0/24 |
RemoteAddr=192.168.1.1/24
Weight
权重处理
详细的使用规则参考官网:Spring Cloud Gateway。如果此时路由规则不符合,浏览器页面可能会出现404错误!为了增加时间路由规则,您需要给order-service增加在2023年后访问才符合规则的断言条件。predicates中的配置项如下:
```yaml
predicates:
- Path=/order/** # 表示路径为/order开头的请求都符合规则
- After=2031-01-20T17:42:47.789-07:00[America/Denver] # 表明在2023年后访问才符合规则
```
执行结果:
1. PredicateFactory的作用是什么?
PredicateFactory的作用是读取用户定义的断言条件,对请求进行解析并做出判断。
2. Path=/user/**是什么含义?
Path=/user/**表示对路径以/user开头的请求进行解析,认为这些请求符合规则。
3. 过滤器工厂 GatewayFilter
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。Spring提供了31种不同的路由过滤器工厂可供选择。例如,您可以使用过滤器工厂为所有进入user-service的请求添加一个名为Truth的请求头,值为"Itcast is freaking awesome!"。需要注意的是,key和value之间是以逗号的方式连接。
验证执行结果:在UserController中使用@RequestHeader注解就可以拿到请求头信息。
重构后的内容如下:
```java
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) {
// 进行打印
System.out.println("Truth: " + truth);
return userService.queryById(id);
}
@Component
public class GlobalFilter implements GatewayFilter, GlobalFilter {
@Override
public Mono
// 获取请求头信息
HttpHeaders headers = exchange.getRequest().getHeaders();
Optional
// 如果请求头中包含Truth字段
if (truthHeader.isPresent()) {
// 获取Truth值
String truthValue = truthHeader.get();
// 进行处理,例如打印等操作
System.out.println("Global Filter: Truth value is " + truthValue);
} else {
// 如果请求头中不包含Truth字段,可以执行其他操作,例如记录日志等
System.out.println("No Truth header found in the request");
}
// 继续执行过滤器链中的下一个过滤器,如果没有下一个过滤器则放行请求并返回空结果
return chain.filter(exchange).then();
}
}
```
在gateway启动类的同包下定义一个名为GlobalFilter的过滤器,我们需要实现以下功能:
1. 判断请求的参数中是否包含authorization字段,且其值为admin;
2. 如果满足条件,则放行请求;否则拦截请求。
首先,我们需要在filter中通过exchange参数获取request对象,然后调用request对象的getQueryParams方法获取到所有的请求参数。接着,从请求参数中通过authorization这个key获取value值admin。如果这个值存在,就调用chain执行链的filter方法,把exchange传下去;如果这个值不存在,就通过exchange参数获取到response对象,通过这个对象的setComplete方法进行拦截。在拦截之前还可以通过通过response方法设置状态码,增加用户的体验感!
为了将这个过滤器纳入Spring的管理,我们需要在过滤器上增加@Component注解组件扫描注解。同时,为了控制过滤器的执行顺序,我们还需要在过滤器上增加@Order注解。
下面是实现这个过滤器的代码:
```java
package com.example.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Order(-1) // 设置过滤器的执行顺序,负数表示优先级高,越靠前执行
public class GlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono
String authorization = exchange.getRequest().getQueryParams().get("authorization");
if ("admin".equals(authorization)) { // 如果authorization值为admin,放行请求
return chain.filter(exchange);
} else { // 否则拦截请求,并设置状态码为401 Unauthorized
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 完成响应
}
}
@Override
public int getOrder() { // 实现Ordered接口,用于设置过滤器的执行顺序
return -1; // 这里设置为负数,表示优先级高,越靠前执行
}
}
```
```java
package cn.itcast.gateway;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component // @Order(-1)
public class AuthrizeFilter implements GlobalFilter, Ordered {
@Override
public Mono
// 第一步:获取所有参数
// 获取request对象
ServerHttpRequest request = exchange.getRequest();
// 获取所有请求参数
MultiValueMap
// 第二步:根据authorization参数获取value值
String auth = params.getFirst("authorization");
// 第三步:判断请求参数的值是不是等于admin
if ("admin".equals(auth)){
// 是,放行
return chain.filter(exchange);
} else {
// 不是,拦截
// 结束之前设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
// 实现Ordered接口的方法也可以
@Override
public int getOrder() {
return -1;
}
}
```
进行访问时,如果不增加参数,将会出现401错误(未登录错误)。为了解决这个问题,我们需要实现全局过滤器。全局过滤器的作用是对所有路由生效的过滤器,可以自定义处理逻辑,比较灵活。
实现全局过滤器的步骤如下:
1. 实现GlobalFilter接口;
2. 添加@Order注解或实现Ordered接口;
3. 添加组件扫描@Component注解;
4. 编写处理逻辑。
在这三个过滤器中,请求进入网关会碰到三类过滤器:当前路由的过滤器、默认的过滤器、全局过滤器。接下来就分析一下这三个过滤器的执行顺序。
需要注意的是,这三个过滤器不是同一种类型,但是它们可以放到同一个集合当中进行排序。这是因为路由过滤器和默认过滤器默认配置方式相同,它们都是AddRequestHeaderGatewayFilterFactory对象,这个过滤器的工厂会读取配置文件生成一个真正的过滤器GatewayFilter。而在FilteringWebHandler类里面有一个FilteringWebHandler(过滤器适配器)这个类适配器实现了GatewayFilter接口,在适配器内部又接收了一个全局过滤器参数GlobalFiter。通过适配器模式进行传参当做GatewayFilter来使用,这样就建立了联系。因此,这三种过滤器都可以认为是GatewayFilter类型,同一种类型就可以放到List集合当中进行排序。
对于如何进行排序的问题,我们已经知道每一个过滤器都必须指定一个int类型的order值。order值越小,优先级越高,执行顺序越靠前。对于全局过滤器GlobalFilter,可以通过实现Ordered接口或者添加@Order注解来指定order值,由我们自己指定。
. 路由过滤器和默认过滤器的顺序
在Spring中,我们并没有指定路由过滤器和默认过滤器的执行顺序。实际上,它们的执行顺序是由Spring框架自动管理的。默认情况下,它们的执行顺序是按照声明顺序从1递增的。例如:
```java
// 声明顺序为1的默认过滤器
@Bean
public DefaultFilter defaultFilter() {
return new DefaultFilter();
}
// 声明顺序为2的路由过滤器
@Bean
public RouteFilter routeFilter() {
return new RouteFilter();
}
```
当过滤器的order值相同时,会按照 `defaultFilter > 路由过滤器 > GlobalFilter` 的顺序执行。具体实现细节可以参考源码:很清晰,可以自己看一下。
1. `org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()` 方法首先加载 `defaultFilters`,然后再加载某个路由的 `filters`,最后将它们合并。
2. `org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()` 方法会加载全局过滤器,与前面的过滤器合并后根据 `order` 排序,组织过滤器链。
6. 跨域问题处理
在微服务架构中,所有的请求都需要先经过网关,然后才能到达具体的微服务。因此,不需要在每个微服务中都处理跨域问题,只需要在网关中进行处理即可。跨域问题主要包括以下几种情况:
- 域名不同:`www.taobao.com`、`www.taobao.org`、`www.jd.com` 和 `miaosha.jd.com`;
- 域名相同,端口不同:`localhost:8080` 和 `localhost:8081`;
- 浏览器禁止跨域请求;
- 请求的发起者与服务端发生跨域;
- AJAX 请求被浏览器拦截的问题。
解决跨域问题的方案是使用 CORS(Cross-Origin Resource Sharing,浏览器去询问服务器的方式)。通过 axios 发送 GET 请求时,请求地址就是网关的地址。
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
axios.get("http://localhost:10010/user/1?authorization=admin")
.then(resp => console.log(resp.data))
.catch(err => console.log(err))
请根据以下步骤进行配置以在8090端口上运行并在控制台查看报错请求:
1. 首先,确保您已经安装了所需的软件和依赖项。
2. 打开终端(命令提示符或PowerShell),输入以下命令以启动服务器:
```
your_server_name_here -p 8090
```
请将`your_server_name_here`替换为您的服务器名称。
3. 在另一个终端窗口中,打开浏览器并输入`http://localhost:8090`。这将在控制台中显示报错请求。
4. 根据报错信息,检查您的代码以找出可能的问题。如果您遇到问题,可以在此帖子下提供更多详细信息,以便我们为您提供帮助。
server:
port: 10010 # 服务端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
gateway: # 服务路由配置
routes:
- id: userservice # 路由标识
uri: http://127.0.0.1:8081 # 路由的目标地址,http是固定地址
uri: lb://user-service # 路由的目标地址,lb是负载均衡,后面跟服务名称
predicates:
- Path=/user/** # 路径断言,判断是否以/user开头
filters:
# 注意key和value之间是以逗号隔开
- AddRequestHeader=Truth,Itcast is freaking awesome!
# orderservice路由配置
- id: orderservice
uri: lb://order-service # 路由的目标地址,lb是负载均衡,后面跟服务名称
predicates:
- Path=/order/**
- Before=2031-01-20T17:42:47.789-07:00[America/Denver]
# 默认过滤器配置
- AddRequestHeader=Truth,Itcast is freaking awesome! # 注意key和value之间是以逗号隔开
globalcors: # 全局的跨域处理配置
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(防止CORS浏览器询问服务器拦截)
corsConfigurations:
corsConfigurations: '[/**]': # 拦截所有的请求
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期,有效期内直接放行
以下是内容重构后的结果:
要实现跨域访问,需要重启网关并设置相应的端口。首先,打开Nginx的配置文件,找到对应的location块,并在其中添加如下代码:
```
add_header 'Access-Control-Allow-Origin' '*'; #允许任何域名访问该资源
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; #允许请求方法
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; #允许请求头
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000; #预检请求的有效期为20分钟
add_header 'Content-Type' 'text/plain charset=UTF-8'; #响应数据类型
add_header 'Content-Length' 0; #响应数据长度
return 204; #返回204状态码表示成功处理请求(无响应内容)
} elseif ($request_method = 'POST') {
... #如果是POST请求,则进行相关处理
} elseif ($request_method = 'GET') {
... #如果是GET请求,则进行相关处理
} elseif ($request_method = 'OPTIONS') {
... #如果是OPTIONS请求,则进行相关处理
} elseif ($request_method = 'HEAD') {
... #如果是HEAD请求,则进行相关处理
} elseif ($request_method = 'PUT') {
... #如果是PUT请求,则进行相关处理
} elseif ($request_method = 'DELETE') {
... #如果是DELETE请求,则进行相关处理
} elseif ($request_method = 'TRACE') {
... #如果是TRACE请求,则进行相关处理
} elseif ($request_method = 'CONNECT') {
... #如果是CONNECT请求,则进行相关处理
} elseif ($request_method = 'PATCH') {
... #如果是PATCH请求,则进行相关处理
} elseif ($request_method = 'PURGE') {
... #如果是PURGE请求,则进行相关处理
} elseif ($request_method = 'TRACK') {
... #如果是TRACK请求,则进行相关处理
} elseif ($request_method = 'CACHE_CONTROL') {
... #如果是CACHE_CONTROL请求,则进行相关处理
} elseif ($request_method = 'IF_MODIFIED_SINCE') {
... #如果是IF_MODIFIED_SINCE请求,则进行相关处理
} elseif ($request_method = 'IF_NONE_MATCH') {
... #如果是IF_NONE_MATCH请求,则进行相关处理
} elseif ($request_method = 'IF_RANGE') {
... #如果是IF_RANGE请求,则进行相关处理
} elseif ($request_method = 'IF_UNMODIFIED_SINCE') {
... #如果是IF_UNMODIFIED_SINCE请求,则进行相关处理
} elseif ($request_method = 'MAX_AGE') {
... #如果是MAX_AGE请求,则进行相关处理
} elseif ($request_method = 'EXPIRES') {
... #如果是EXPIRES请求,则进行相关处理
} elseif ($request_method = 'LAST_MODIFIED') {
.