SpringCloud集成微服务API网关Gateway(详解)
引言:
SpringCloud Alibaba 系列目录
一、搭建SpringCloud工程,请前往:保姆级教程构建SpringCloud工程(图文结合)
二、集成服务注册配置管理中心Nacos,请前往:SpringCloud集成服务注册配置管理中心Nacos
三、集成HTTP客户端工具OpenFeign,请前往:SpringCloud集成HTTP客户端工具OpenFeign
四、集成统一网关SpringCloud Gateway,请前往:SpringCloud集成微服务API网关Gateway(详解)
集成统一网关Gateway
1.为什么需要网关和其作用?
- 路由转发: 网关可以作为所有客户端请求的入口,根据请求的URL路径将请求路由到相应的微服务实例。通过网关,可以将客户端请求分发到不同的微服务中,实现了微服务之间的解耦。
- 负载均衡: 网关可以对请求进行负载均衡,将请求分发到多个相同服务的实例中。这样可以提高系统的性能和可用性,并且有效地利用了系统资源。
- 安全认证: 网关可以作为安全认证的入口,对请求进行身份验证、授权和安全检查。通过网关,可以集中管理系统的安全策略,保护微服务免受未经授权的访问。
- 流量控制: 网关可以实现流量控制进行限流,限制对微服务的访问速率,防止突发流量对系统造成影响。可以基于请求频率、并发连接数等指标对流量进行控制和限制。
- 监控和日志: 网关可以记录所有的请求和响应信息,并且可以进行统一的监控和日志记录。这样可以方便开发人员进行故障排查、性能优化和系统分析。
2.技术实现
在SpringCloud中网关的实现有两种:
- Gateway 实现
- Zuul 实现
Zuul是基于Servlet的实现,属于阻塞式编程。
SpringCloud Gateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
3. 搭建网关服务
-
创建新的 cloud-gateway Module,引入SpringCloud Gateway的依赖及Nacos的服务注册与发现依赖
org.springframework.cloud spring-cloud-starter-gateway com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery
-
在Gateway 的 resources 下新建 bootstrap.yml,配置路由和Nacos 服务注册与发现中心
这里如果创建的是bootstrap.yml的话,一定要引入 bootstrap的依赖,不然是不会进行识别和加载的,也可以不引入 bootstrap依赖,直接创建application.yml也是可以的
org.springframework.cloud spring-cloud-starter-bootstrap
网关路由配置的内容包括:
- 路由Id:唯一的路由标识
- uri:目标路由地址,支持http和lb两种
- predicates:路由断言,判断请求是否符合规则,符合路由规则在进行转发到目标路由地址
- filters:路由过滤器,处理请求或者响应
# 服务端口 server: port: 10717 spring: application: # 服务名称 name: gateway profiles: active: dev main: # Description: # Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway. # Action: # Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency. #上述错误表明 SpringBoot 与 SpringCloud Gateway 不兼容, 设置属性为reactive 告诉Spring Boot使用响应式Web应用程序类型,与Spring Cloud Gateway兼容。 web-application-type: reactive # cloud: nacos: # Nacos服务地址 server-addr: localhost:8848 # Nacos的服务注册与发现 discovery: # Nacos服务地址 # server-addr: localhost:8848 # 配置集群名称,也就是机房位置,例如:HZ 杭州 cluster-name: HZ # Nacos可视化命名空间ID namespace: cdd2a801-3280-4476-8362-d46318aac1a3 gateway: routes: # 网关路由配置 - id: orderservice # 路由ID,自定义填写,唯一即可 # uri 有两种配置方法,一种 http ,一种 lb:服务名称 # uri: http://127.0.0.1:18088/ # 路由目标地址 uri: lb://orderservice # 路由的目标地址 lb 就是负载均衡,后边跟服务名称 predicates: #路由断言 ,白话就是判断请求符不符合路由规则条件 - Path=/orderservice/** # 这个是按照路径匹配,只要以 /orderservice/ 开头就符合条件 - id: paymentservice uri: lb://paymentservice predicates: - Path=/paymentservice/**
-
新建GatewayApplication
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author waves * @date 2024/6/4 15:53 */ @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); System.out.println("Gateway网关服务启动成功 ヾ( ̄ー ̄)X(^▽^)ゞ"); } }
启动网关服务,查看是否正常启动。
4.路由断言工厂
路由断言工厂 Route Predicate Factory
- 在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断条件
- 在SpringCloud Gateway 断言工厂有十几个,Spring 提供了十一种基本的Predicate工厂
- 例如 - Path=/paymentservice/** 是按照路径匹配,这个规则是lorg.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
5.Spring 提供了十一种基本的Predicate工厂
名称 说明 示例 After 是某个时间点后的请求 - After=2024-01-20T19:41:47.789-07:00[America/Denver] Before 是某个时间点之前的请求 - Before=2024-04-13T19:14:47.433+08:00[Asia/Shanghai] Between 是某两个时间点之前的请求 - Between=2023-01-20T17:42:47.789-07:00[America/Denver], 2024-10-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 权重处理 6.GatewayFilter 路由过滤器
GatewayFilter是SpringCloud Gateway 网关中提供的一种过滤器,对进入网关的请求和服务响应进行处理。
- 过滤器的作用是什么?
- 对服务的路由请求或响应进行处理,例如添加请求头,添加前缀路径等
- 配置在某个服务路由下的过滤器只对当前路由的请求生效
- default-filters 默认过滤器的作用?
- 对配置在网关的所有路由都生效的过滤器
1.Spring 提供了31种不同的路由过滤器工厂。
如下:
名称 说明 AddRequestHeaderGatewayFilterFactory 用于添加请求头。 AddResponseHeaderGatewayFilterFactory 用于添加响应头。 AddRequestParameterGatewayFilterFactory 用于添加请求参数。 AddRequestParameterGatewayFilterFactory 用于添加响应参数。 AddResponseHeaderGatewayFilterFactory 用于添加请求参数。 AddRequestParameterGatewayFilterFactory 用于添加响应参数。 PrefixPathGatewayFilterFactory 用于添加前缀路径。 PreserveHostHeaderGatewayFilterFactory 用于保留主机头。 RemoveNonProxyHeadersGatewayFilterFactory 用于删除非代理头。 RemoveRequestHeaderGatewayFilterFactory 用于删除请求头。 RemoveResponseHeaderGatewayFilterFactory 用于删除响应头。 RequestRateLimiterGatewayFilterFactory 用于请求速率限制。 RetryGatewayFilterFactory 用于请求重试。 SecureHeadersGatewayFilterFactory 用于添加安全头。 SetPathGatewayFilterFactory 用于设置路径。 SetPathGatewayFilterFactory 用于设置路径。 SetRequestHeaderGatewayFilterFactory 用于设置请求头。 SetResponseHeaderGatewayFilterFactory 用于设置响应头。 SetStatusGatewayFilterFactory 用于设置状态码。 RedirectToGatewayFilterFactory 用于重定向。 RewritePathGatewayFilterFactory 用于重写路径。 RewriteLocationResponseHeaderGatewayFilterFactory 用于重写响应头中的位置。 SaveSessionGatewayFilterFactory 用于保存会话。 MapRequestHeaderGatewayFilterFactory 用于映射请求头。 MapResponseHeaderGatewayFilterFactory 用于映射响应头。 MapRequestParameterGatewayFilterFactory 用于映射请求参数。 MapResponseHeaderGatewayFilterFactory 用于映射响应参数。 StripPrefixGatewayFilterFactory 用于删除前缀。 AddResponseHeaderGatewayFilterFactory 用于删除前缀。 AddRequestParameterGatewayFilterFactory 用于删除前缀。 AddRequestParameterGatewayFilterFactory 用于删除前缀。 2.测试AddRequestHeader
以AddRequestHeaderGatewayFilterFactory 为例:给进入orderservice 的请求添加一个请求头 Authorization = token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA
在Gateway的 bootstrap.yml 文件中,在orderservice 的配置下添加路由过滤器
spring: cloud: gateway: routes: # 网关路由配置 - id: orderservice # 路由ID,自定义填写,唯一即可 # uri 有两种配置方法,一种 http ,一种 lb:服务名称 # uri: http://127.0.0.1:18088/ # 路由目标地址 uri: lb://orderservice # 路由的目标地址 lb 就是负载均衡,后边跟服务名称 predicates: #路由断言 ,白话就是判断请求符不符合路由规则条件 - Path=/orderservice/** # 这个是按照路径匹配,只要以 /orderservice/ 开头就符合条件 # 路由过滤器 filters: # 对进入 orderservice 的请求添加 请求头 - AddRequestHeader=Authorization,token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA
在代码中测试是否能获取到请求头
/** * 测试获取gateway中添加的 Authorization * @param request * @return */ @GetMapping("/getToken") public ResponseResult getToken(HttpServletRequest request){ return ResponseResult.success("操作成功",request.getHeader("Authorization")); } 返回结果,成功获取到token http://localhost:10717/orderservice/system/order/getToken { "code": 100, "data": "token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA", "message": "操作成功" }
3.默认过滤器
如果要对所有的路由都添加过滤器,可以把过滤器工厂配置在default下。如下配置:
spring: application: # 服务名称 name: gateway profiles: active: dev cloud: gateway: routes: # 网关路由配置 - id: orderservice # 路由ID,自定义填写,唯一即可 # uri 有两种配置方法,一种 http ,一种 lb:服务名称 # uri: http://127.0.0.1:18088/ # 路由目标地址 uri: lb://orderservice # 路由的目标地址 lb 就是负载均衡,后边跟服务名称 predicates: #路由断言 ,白话就是判断请求符不符合路由规则条件 - Path=/orderservice/** # 这个是按照路径匹配,只要以 /orderservice/ 开头就符合条件 # 路由过滤器 filters: # 对进入 orderservice 的请求添加 请求头 - AddRequestHeader=Authorization,token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA - id: paymentservice uri: lb://paymentservice predicates: - Path=/paymentservice/** # 默认过滤器,会对所有的路由请求都生效 default-filters: - AddRequestHeader=Authorization,token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA
代码测试,paymentservice服务未单独配置添加请求头,进行测试
/** * 测试获取gateway中添加的 Authorization * @param request * @return */ @GetMapping("/getToken") public ResponseResult getToken(HttpServletRequest request){ return ResponseResult.success("操作成功",request.getHeader("Authorization")); } 返回结果,成功获取到token http://localhost:10717/paymentservice/payment/getToken { "code": 100, "data": "token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA", "message": "操作成功" }
4.全局过滤器
全局过滤器 GlobalFilter 的作用 与 Gateway Filter 的作用一样。处理一切进入网关的请求和服务的响应。
两个的区别在于GatewayFilter是通过配置定义,处理逻辑是不可变的。而GlobalFilter的逻辑需要开发人员自己写代码进行实现。方式是实现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 * @param chain provides a way to delegate to the next filter * @return {@code Mono} to indicate when request processing is complete * * 处理Web请求并(可选地)通过给定的GatewayFilterChain委托给下一个WebFilter。 * Params: exchange-请求上下文-提供了一种委托给下一个过滤器的方法 * 返回:Mono指示请求处理何时完成 * */ Mono filter(ServerWebExchange exchange, GatewayFilterChain chain); }
自定义过滤器实现代码:
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.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) // 优先级 配置方式有两种,一种直接加 @Order(-1) 注解,另一种实现Order接口 * @author waves * @date 2024/6/5 14:36 */ @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String STATIC_TOKEN = "token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA"; @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1.获取请求中的参数 ServerHttpRequest request = exchange.getRequest(); MultiValueMap params = request.getQueryParams(); //2.获取请求中的Authorization 参数值 String authToken = params.getFirst("Authorization"); //3.判断token是否合法 if (STATIC_TOKEN.equals(authToken)){ //4.合法,进行放行 return chain.filter(exchange); } //5.不合法,直接拦截,并返回状态码 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); //拦截请求 return exchange.getResponse().setComplete(); } @Override public int getOrder() { return -1; } }
测试:
错误请求: http://localhost:10717/paymentservice/payment/getToken?Authorization=1234 返回: This page isn’t workingIf the problem continues, contact the site owner. HTTP ERROR 401 正确请求: http://localhost:10717/paymentservice/payment/getToken?Authorization=token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA 返回: {"code":100,"data":"token-Bearer-5S3ZURuoTkN95zkG1QQ7eo7AHS7WA","message":"操作成功"}
5.过滤器的执行顺序
当请求进入到网关会经过三类过滤器:针对配置的路由过滤器(filters:)、默认过滤器(default-filters:)、全局过滤器(GlobalFilter)
请求进入网关路由后,会将当前路由过滤器和默认过滤器,GlobalFilter合并到一个过滤器链(集合)中,进行排序后依次执行
当请求进入时
请求 --> 路由 --> 默认过滤器 --> 服务路由过滤器 --> 全局过滤器 --> 微服务
响应 --> 全局过滤器 --> 服务路由过滤器 --> 默认过滤器 --> 路由
1.自定义GlobalFilter 通过添加@Order注解指定order值,或者实现 Ordered接口 返回order的值。
2.每一个过滤器都必须指定一个int类型的order值,值越小,优先级就越高,执行顺序就越靠前。
3.路由过滤器和默认过滤器的order由spring指定,默认是按照声明顺序从1递增。
4.当过滤器的order值都一样的时候,会按照 默认过滤器 > 路由过滤器 > 全局过滤器 的顺序执行。
具体可以参考如下几个类的源码进行查看:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。 org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
7.跨域问题处理
什么是跨域:
当一个网页的源(origin)与所请求资源的源不一致时产生的问题。
跨域包括:
- 域名不同: 例如 www.baidu.com 和 fanyi.baidu.com
- 域名相同但端口不同:localhost:8088 和 localhost:18088
原因:
跨域时浏览器会执行同源策略(Same-Origin Policy),限制页面中的JavaScript代码只能与其同源的资源进行交互,否则的话请求就会被拦截
解决方案:
- 使用CORS(跨域资源共享):服务端设置合适的 CORS 头部,允许特定的源访问资源。通过设置 Access-Control-Allow-Origin 头部,服务器可以允许指定源的请求访问资源。
- 代理:在同源的服务器上设置代理,将跨域请求转发到目标服务器,然后将响应返回给客户端。这种方法可以绕过同源策略限制,但需要在服务器端进行额外的配置。
- 反向代理:在服务器端配置反向代理,将客户端请求发送到目标服务器,并将响应返回给客户端。这种方法可以隐藏目标服务器的真实地址,同时解决跨域问题。
- JSONP(JSON with Padding):JSONP 是通过动态添加 标签实现跨域请求的一种方法。由于 标签的跨域特性,可以通过在请求 URL 中添加回调函数名来获取数据,并在响应中返回 JSON 数据。
- iframe:通过在页面中嵌入 iframe,将目标页面作为 iframe 的 src,通过 window.postMessage() 方法进行通信,可以实现跨域通信。
- WebSocket:WebSocket 提供了一种在不同源之间进行双向通信的方式,可以用于跨域通信。
这里网关使用CORS解决 跨域问题,并且在Gateway中只需要简单配置即可实现:
spring: cloud: gateway: # 全局跨域处理 globalcors: # 解决options(预检请求)请求被拦截问题,默认是拦截 add-to-simple-url-handler-mapping: true cors-configurations: '[/**]': allowedOrigins: # 允许哪些网站的跨域请求 - "http://localhost:10717" - "http://localhost:8088" - "https://localhost:8099" allowedMethods: #允许跨越的Ajax请求 - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" # 允许在请求头中携带头信息 allowCredentials: true # 是否允许携带Cookie maxAge: 360000 # 跨域检测的有效时间
- 过滤器的作用是什么?
-