1. Gateway
1.1. 什么是网关
1.1.1. 网关的功能
- 身份认证
- 权限校验
- 服务路由
- 负载均衡
- 请求限流
1.1.2. Spring Cloud网关落地方案
- Zuul是基于Servlet实现的,属于阻塞式编程
- Gateway是基于Spring5中提供的 spring-webflux 实现的,属于响应式编程,性能要由于Zuul
1.2. 搭建网关服务
1.2.1. 创建一个gateway-service服务
引入gateway的依赖:spring-cloud-starter-gateway不需要spring-boot-starter-web;如果他们同时存在则gateway不可用。故我们需要把父工程中的spring-boot-starter-web排除,其它子模块重新引入web包即可。
<?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">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>com.acx</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.acx</groupId>
<artifactId>gateway-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
1.2.2. 编写网关配置
server:
port: 10010
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ
gateway:
routes:
- id: order-service # 自定义路由id,必须唯一
uri: lb://order-service # lb是负载均衡(默认是轮询)的意思,后面跟的是服务名称
predicates:
- Path=/order/**,/order/api/** # 这是路由断言;只要是以/orderservice或者/orderservice/api开头的请求就会被路由到order-service服务上
- id: product-service # 商品服务路由id
uri: lb://product-service
predicates:
- Path=/product/**,/product/api/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**,/user/api/**
1.2.3. 测试网关是否生效
- 访问http://localhost:10010/order/getOne
2. Gateway原理
2.1. 网关处理流程
2.2. 断言工厂(PathRoutePredicateFactory)
作用:读取断言规则并处理,转变为路由判断的条件,最后规则由PathRoutePredicateFactory断言工厂处理。
11种断言工厂:
断言工厂 | 说明 |
---|---|
After | 某个时间点之后请求 |
Before | 某个时间点之前请求 |
Between | 某两个时间点之间请求 |
Cookie | 请求必须包含某些cookie |
Header | 请求必须包含某些header |
Host | 请求必须是指定方式 |
Method | 请求必须是指定方法:GET、POST |
Path(默认) | 请求路径必须是指定路由规则 |
Query | 请求必须包含指定参数 |
RemoteAddr | 请求这IP必须为指定范围 |
Weight | 权重处理 |
11种断言示例:具体的示例可以查询官网文档https://www.springcloud.cc/spring-cloud-greenwich.html#gateway-request-predicates-factories
# After
predicates:
- After=2022-01-20T17:42:47.789-07:00[America/Denver]
# Before
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
# Between
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
# Cookie
predicates:
- Cookie=chocolate, ch.p # 使用Cookie路由断言工厂,配置cookie,正则表达式(可有可无)
# Header
predicates:
- Header=X-Request-Id, \d+
# Host
predicates:
- Host=**.somehost.org,**.anotherhost.org
# Method
predicates:
- Method=GET,POST
# Query
predicates:
- Query=green
# RemoteAddr
predicates:
- RemoteAddr=192.168.1.1/24
# Weight
predicates:
- Weight=group1, 2
2.3. 路由过滤器配置(GatewayFilter)
2.3.1. 请求过滤器流程图
作用:过滤器链对进入网关的请求和微服务返回的响应做处理
2.3.2. 过滤器工厂
Spring Cloud Gateway官网实例:https://www.springcloud.cc/spring-cloud-greenwich.html#_addrequestheader_gatewayfilter_factory
常用的过滤器工厂:
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
2.3.3. 局部配置过滤器
已AddRequestHeader拦截器为例
网关服务做如下配置:
server:
port: 10010
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ
gateway:
routes:
- id: order-service # 自定义路由id,必须唯一
uri: lb://order-service # lb是负载均衡(默认是轮询)的意思,后面跟的是服务名称
predicates:
- Path=/order/**,/order/api/** # 这是路由断言;只要是以/orderservice或者/orderservice/api开头的请求就会被路由到order-service服务上
filters:
- AddRequestHeader=abc,hello word # 给order服务配置拦截器
- id: product-service # 商品服务路由id
uri: lb://product-service
predicates:
- Path=/product/**,/product/api/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**,/user/api/**
Order服务添加获取abc请求头代码:
package com.acx.controller;
import com.acx.client.UserClient;
import com.acx.pojo.vo.ActorInfoVO;
import com.acx.pojo.vo.OrderInfoVO;
import com.acx.pojo.vo.StudentVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
@Autowired
private StudentVO studentVO;
@GetMapping("getStudent")
public StudentVO getStudent() {
return studentVO;
}
@GetMapping("getOne")
public OrderInfoVO getOne(@RequestHeader("abc") String abc) {
logger.info("开始查询订单");
OrderInfoVO orderInfoVO = new OrderInfoVO();
orderInfoVO.setOrderName("订单123");
orderInfoVO.setOrderSn("046b399937ad4271bcd5ed275f2b4682");
orderInfoVO.setProductName("商品123");
orderInfoVO.setProductNum(23);
int userId = 1;
// String getUserUrl = "http://127.0.0.1:8083/user/getUser/" + userId;
// String getUserUrl = "http://user-service/user/getUser/" + userId;
//服务发现
// ActorInfoVO actor = restTemplate.getForObject(getUserUrl, ActorInfoVO.class);
ActorInfoVO actor = userClient.getUser(userId);
orderInfoVO.setUser(actor);
logger.info("测试AddRequestHeader拦截器是否生效,abc={}", abc);
return orderInfoVO;
}
}
请求Order接口测试:http://localhost:10010/order/getOne
- 控制台打印日志如下:说明过滤器生效了
2022-05-09 22:41:58.698 INFO 19452 --- [nio-8081-exec-1] com.acx.controller.OrderController : 测试AddRequestHeader拦截器是否生效,abc=hello word
2.4. 全局默认过滤器(DefaultFilter)
- 所有经过此网关路由的服务器请求都会对此过滤器生效
server:
port: 10010
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ
gateway:
routes:
- id: order-service # 自定义路由id,必须唯一
uri: lb://order-service # lb是负载均衡(默认是轮询)的意思,后面跟的是服务名称
predicates:
- Path=/order/**,/order/api/** # 这是路由断言;只要是以/orderservice或者/orderservice/api开头的请求就会被路由到order-service服务上
- id: product-service # 商品服务路由id
uri: lb://product-service
predicates:
- Path=/product/**,/product/api/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**,/user/api/**
default-filters:
- AddRequestHeader=abc,hello word # 全局配置拦截器
2.5. 全局过滤器(GlobalFilter)
2.5.1. 什么是全局过滤器
作用:处理一切进入网关服务的请求和响应,与GatewayFilter作用一样,区别在于GlobalFilter支持自定义逻辑扩展。
核心方法:
public interface GlobalFilter {
/**
* Process the Web request and (optionally) delegate to the next {@code WebFilter}
* through the given {@link GatewayFilterChain}.
* @param exchange the current server exchange
* exchange: 请求上下文,里面包含了Request、Response等信息
* @param chain provides a way to delegate to the next filter
* chain: 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} to indicate when request processing is complete
* MonoZ: 返回时表示当前过滤器逻辑流程结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
2.5.2. 应用:自定义一个登陆验证拦截器
Gateway网关服务:自定义认证拦截器,此拦截器集成GlobalFilter
package com.acx.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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;
@Order(1) //拦截器优先级;值越小优先级越高
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request
ServerHttpRequest request = exchange.getRequest();
//2.获取请求参数
MultiValueMap<String, String> params = request.getQueryParams();
//3.获取token
String token = params.getFirst("token");
if ("login".equals(token)) {
//4.是、请求通过
return chain.filter(exchange);
}
//5.否、校验未通过
//5.1.设置校验未通过code 401 认证未通过
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
测试结果:认证失败
测试结果:认证成功
2.5. 过滤器执行顺序
前言:请求到达网关服务后,一共要经过路由过滤器、DefaultFilter、GlobalFilter三种过滤器组成的过滤器链。他们会有一个执行顺序,排序后一次执行每个过滤器。
规则:
- 当Order值一样时DefaultFilter > 路由过滤器 > GlobalFilter。
- Order值越小、Filter优先级越高。
Order值:
- GlobalFilter的order值可以自己进行指定。
- DefaultFilter和路由过滤器的Order值是由Spring决定的,默认按照声明顺序从1递增。
实例如下:
server:
port: 10010
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ
gateway:
routes:
- id: order-service # 自定义路由id,必须唯一
uri: lb://order-service # lb是负载均衡(默认是轮询)的意思,后面跟的是服务名称
predicates:
- Path=/order/**,/order/api/** # 这是路由断言;只要是以/orderservice或者/orderservice/api开头的请求就会被路由到order-service服务上
filters:
- AddRequestHeader=zhangsan,abc123 # order = 1,越往后Order值就越大
- id: product-service # 商品服务路由id
uri: lb://product-service
predicates:
- Path=/product/**,/product/api/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**,/user/api/**
default-filters:
- AddRequestHeader=abc,hello word # 全局配置拦截器;order=1,越往后Order值就越大
2.6. 网关的跨域问题处理
2.6.1. 什么是跨域问题
浏览器禁止请求发送者与服务端发生卡与Ajax请求;请求域名或者端口与后台目标域名地址不一样时就会出现这种问题。
例子:下面的情况就会出现cors跨域问题
- 游览器请求地址:127.0.0.1:8080
- 目标后台地址:127.0.0.1:10100
2.6.2. 配置文件解救跨域问题
spring:
cloud:
gateway:
# 允许跨域请求配置
globalcors:
cors-configurations:
'[/**]':
# 允许任何域名使用
allowedOrigins: "*"
# 允许任何头
allowedHeaders: "*"
# 允许任何方法(post、get等)
allowedMethods: "*"
# sessionid 多次访问一致
allowCredentials: true
# 允许来自所有域名(allowedOrigins)的所有请求方式(allowedMethods)发出CORS请求
add-to-simple-url-handler-mapping: true # 允许来自所有域名(allowedOrigins)的所有请求方式(allowedMethods)发出CORS请求7
2.6.3. 代码解决跨域问题
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 允许任何域名使用
corsConfiguration.addAllowedOrigin("*");
// 允许任何头
corsConfiguration.addAllowedHeader("*");
// 允许任何方法(post、get等)
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}