赞
踩
网关,简而言之,是一个系统的单一入口点,它接收所有外部请求,并根据请求的特性(如路径、HTTP方法等)将其转发给内部的各个微服务。
比如说,你想要向一个小区内的一个朋友送信,必须要经过看门大爷的同意,大爷如果认为好人,就会把信带给你要送的人;如果大爷认为你是坏人,就会拦截你。
基本步骤:
创建一个新模块gatewawy
:
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos 服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
gateway
模块:
server:
port: 8080
spring:
application:
name: gateway-service # 服务名称
cloud:
nacos:
server-addr: 192.168.56.101:8848 # Nacos服务注册中心的地址和端口
gateway:
routes:
- id: user # 路由的唯一标识符,自定义,需要保证唯一性
uri: lb://user-service # 路由的目标服务地址,lb表示使用负载均衡,user-service是注册中心中的服务名
predicates: # 路由断言,用来判断请求是否符合路由规则
- Path=/users/**,/addresses/** # 路由断言的路径模式,匹配以/users/或/addresses/开头的请求路径
解析:
在routes
部分,定义了一条路由规则,该规则将所有符合指定路径模式的请求路由到user-service
服务。这条规则使用了路径断言Path
,匹配所有以/users/
或/addresses/
开头的请求。通过使用负载均衡lb://
前缀,网关能够根据注册中心中的服务实例信息进行负载均衡。
user-service
模块:
server:
port: 8084
spring:
application:
name: user-service
cloud:
nacos:
server-addr: 192.168.56.101:8848
测试接口:
@ApiOperation("网关测试接口")
@GetMapping("/gateway-test")
public String testGateway(){
return "网关测试成功";
}
nacos:
测试:
在前面的gateway
的routes
部分有四个属性可以配置:
id
:路由的唯一标示predicates
:路由断言,其实就是匹配条件filters
:路由过滤条件uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory
(断言工厂) 读取并处理,转变为路由判断的条件。例如 Path=/user/**
是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,这里重点关注路由断言predicates
:
名称 | 说明 | 示例 |
---|---|---|
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
内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway
内部工作的基本原理。
如图所示:
HandlerMapping
对请求做判断,找到与当前请求匹配的路由规则(Route
),然后将请求交给WebHandler
去处理。WebHandler
则会加载当前路由下需要执行的过滤器链(Filter chain
),然后按照顺序逐一执行过滤器(后面称为**Filter
**)。Filter
被虚线分为左右两部分,是因为Filter
内部的逻辑分为pre
和post
两部分,分别会在请求路由到微服务之前和之后被执行。Filter
的pre
逻辑都依次顺序执行通过后,请求才会被路由到微服务。Filter
的post
逻辑。观察得知:如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter
之前,就能解决前面的问题。
网关过滤器链中的过滤器有两种:
GatewayFilter
:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
.GlobalFilter
:全局过滤器,作用范围是所有路由,不可配置。下面是一些常用的Gateway Filter
和Global Filter
:
Gateway
中内置了很多的GatewayFilter
,详情使用可以参考官方文档:
例如AddRequestHeaderGatewayFilterFacotry
,顾明思议,就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。
使用的使用只需要在application.yaml
中这样配置:
spring:
cloud:
gateway:
routes:
- id: user
uri: lb://user-service
predicates:
-Path=/users/**
filters:
- AddRequestHeader=key, value # 逗号之前是请求头的key,逗号之后是value
如果想要让过滤器作用于所有的路由,则可以这样配置:
spring:
cloud:
gateway:
default-filters: # default-filters下的过滤器可以作用于所有路由
- AddRequestHeader=key, value
routes:
- id: user
uri: lb://user-service
predicates:
-Path=/users/**
GatewayFilterFactory
类自定义GatewayFilter
不是直接实现GatewayFilter
,而是实现AbstractGatewayFilterFactory
。最简单的方式是这样的:
@Component public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求 ServerHttpRequest request = exchange.getRequest(); // 编写过滤器逻辑 System.out.println("过滤器执行了"); // 放行 return chain.filter(exchange); } }; } }
注意:该类的名称一定要以GatewayFilterFactory
为后缀!!!
因为在Spring框架中,很多自动配置功能都是基于约定大于配置的原则,比如命名约定。在这里,如果你实现AbstractGatewayFilterFactory来编写类并以GatewayFilterFactory为后缀命名类的话,Spring Cloud Gateway会自动扫描并识别出那些以GatewayFilterFactory为后缀的Bean然后自动注册加载,不再需要手动配置和加载,大大简化了开发者的配置工作。如果不按照命名约定命名的话多了一步手动配置和加载。
spring:
cloud:
gateway:
default-filters:
- PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器
另外,这种过滤器还可以支持动态配置参数,不过实现起来比较复杂,示例:
GatewayFilterFactory
类@Component public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型 extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> { @Override public GatewayFilter apply(Config config) { // OrderedGatewayFilter是GatewayFilter的子类,包含两个参数: // - GatewayFilter:过滤器 // - int order值:值越小,过滤器执行优先级越高 return new OrderedGatewayFilter(new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取config值 String a = config.getA(); String b = config.getB(); String c = config.getC(); // 编写过滤器逻辑 System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("c = " + c); // 放行 return chain.filter(exchange); } }, 100); } // 自定义配置属性,成员变量名称很重要,下面会用到 @Data static class Config{ private String a; private String b; private String c; } // 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取 @Override public List<String> shortcutFieldOrder() { return List.of("a", "b", "c"); } // 返回当前配置类的类型,也就是内部的Config @Override public Class<Config> getConfigClass() { return Config.class; } }
spring:
cloud:
gateway:
default-filters:
- name: PrintAny # 自定义过滤器的名称
args: # 过滤器传递的参数,手动指定参数名,无需按照参数顺序
a: 1
b: 2
c: 3
自定义GlobalFilter则简单很多,直接实现GlobalFilter即可,而且也无法设置动态参数:
@Component public class PrintAnyGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 编写过滤器逻辑 System.out.println("未登录,无法访问"); // 放行 // return chain.filter(exchange); // 拦截 ServerHttpResponse response = exchange.getResponse(); response.setRawStatusCode(401); return response.setComplete(); } @Override public int getOrder() { // 过滤器执行顺序,值越小,优先级越高 return 0; } }
跨域问题(Cross-Origin Resource Sharing, CORS)是指由于浏览器的同源策略(Same-Origin Policy)限制,一个源(域名、协议和端口)的网页无法向另一个源发送HTTP请求的问题。同源策略是一种安全机制,旨在防止恶意网站访问敏感数据。
同源的定义是:如果两个页面的协议、域名(或IP地址)和端口都相同,则它们具有相同的源。例如,以下两个URL具有相同的源:
http://example.com:80/index.html
http://example.com:80/page.html
而以下URL与上面的URL不同源:
https://example.com:80/index.html
(协议不同)http://www.example.com:80/index.html
(域名不同)http://example.com:81/index.html
(端口不同)当尝试从不同源的客户端发送请求到服务器时,浏览器会阻止这些请求,除非服务器明确允许跨源请求。这就是所谓的跨域问题。
全局CORS配置:在网关的配置文件中,可以定义全局的CORS配置,这些配置会应用到所有的路由上。
spring: application: name: gateway cloud: nacos: server-addr: 192.168.56.101:8848 gateway: globalcors: add-to-simple-url-handler-mapping: true # 因为ajax发起者会通过options请求来向服务器询问是否允许跨域,所以需要设置为true来解决options请求被拦截问题 corsConfigurations: '[/**]': allowedOrigins: "http://www.xxx.com" # 允许那些网站跨域访问 allowedMethods: # 允许的HTTP方法 - GET - POST - PUT - DELETE allowedHeaders: "*" # 允许的请求头 allowCredentials: true # 是否允许发送Cookie maxAge: 3600 # 预检请求的缓存时间(秒)
本文深入探讨了Spring Cloud Gateway网关的核心概念及其在微服务架构中的快速部署方法,除此之外着重讲解了Gateway过滤器(Filters)的重要性以及相关使用,包括过滤器的种类、自定义过滤器的实现以及过滤器在解决跨域问题中的作用。
至于具体的应用要根据实际开发要求编写,这里就不过多展示了,希望对大家有所帮助。
参考资料:
Gateway网关 - wenxuehai - 博客园 (cnblogs.com)
gateway Strict-origin-when-cross-origin跨域问题解决 | 少将全栈 (whatled.com)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。