网关springcloudgateway
上面的架构,会存在着诸多的问题:
客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
认证复杂,每个服务都需要独立认证。
存在跨域请求,在一定场景下处理相对复杂。
网关可以做什么?
- 路由转发。
- 身份认证。
- 统一跨域解决。
- 黑白名单ip
- 敏感词
- 限流
1.常用的网关
nginx:它可以当网关
zuul:早期的微服务就是使用的该组件作为网关,但是它的底层使用的servlet。它的效率非常慢。而且它是netflix的产品。netflix预计产品zuul2,但是zuul2夭折。
springcloudgateway:它是spring公司出品的网关。它的效率是zuul的1.6倍。
2.springcloudgateway
SpringCloudGateway是Spring公司基于Spring5.0,SpringBoot2.0和ProjectReactor等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。它的目标是替代NetflixZuul,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控和限流。
3.如何使用
其实网关它也是一个微服务,那么我们也可以创建网关微服务。
引入spring-cloud-starter-gateway
<dependencies><!--这里引入了gateway的依赖后,不能引用spring-boot-starter-web依赖。因为:gateway它使用的是netty服务器。spring-boot-starter-web里面内置了tomcat服务器.--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency></dependencies>
(2)创建主启动类
@SpringBootApplicationpublicclassGatewayApp{publicstaticvoidmain(String[]args){SpringApplication.run(GatewayApp.class,args);}}
(3)修改配置文件
#配置路由spring:cloud:gateway:routes:-id:shop-product#路由的唯一标识。如果没有给定默认按照UUID生成uri:http://localhost:8001#真实转发的地址predicates:#断言如果断言满足要求,则转发到uri指定的真实地址.-Path=/product/**#如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。-id:shop-orderuri:http://localhost:9001predicates:-Path=/order/**
(4)启动gateway
(5)演示
4.gateway负载均衡转发
上面配置文件有没有需要改进的?
- 我们真实转发的地址,万一搭建是一个集群。我们观察到gateway本身也是一个微服务,是否可以从注册中心拉取相关的微服务,然后访问该服务呢。
(1)引入nacos注册中心的依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
(2)修改配置文件
测试:
5.简洁版
#配置路由spring:cloud:gateway:routes:-id:shop-product#路由的唯一标识。如果没有给定默认按照UUID生成uri:lb://shop-product#真实转发的地址lb:---loadbalancedpredicates:#断言如果断言满足要求,则转发到uri指定的真实地址.-Path=/product/**#如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。-id:shop-orderuri:lb://shop-orderpredicates:-Path=/order/**
思考:如果这时增加新的微服务,需要修改网关的路由配置。
改为自动路由发现。
(1)修改gateway的配置文件
(2)访问网关
6.gateway流程
6.1断言的种类
l基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
-After=2019-12-31T23:59:59.78908:00[Asia/Shanghai]
l基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
-RemoteAddr=192.168.1.1/24
l基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie名字和一个正则表达式。判断请求
cookie是否具有给定名称且值与正则表达式匹配。
-Cookie=chocolate,ch.
l基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否
具有给定名称且值与正则表达式匹配。keyvalue
-Header=X-Request-Id,\d
l基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
l基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
-Method=GET
l基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
-Path=/foo/{segment}基于Query请求参数的断言工厂
QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式,判断请求参数是否具
有给定名称且值与正则表达式匹配。
-Query=baz,ba.
l基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发
routes:
-id:weight_route1uri:host1predicates:
-Path=/product/**
-Weight=group3,1
-id:weight_route2uri:host2predicates:
-Path=/product/**
-Weight=group3,9
如果上面的内置断言无法满足需求可以自定义断言。【了解】
案例:年龄必须在18~65之间才能访问我指定的微服务。
自定义断言类
packagecom.aaa.predicate;importorg.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;importorg.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.server.ServerWebExchange;importjavax.validation.constraints.NotNull;importjava.time.ZonedDateTime;importjava.util.Arrays;importjava.util.List;importjava.util.function.Predicate;/***@program:qy156-shop-parent*@description:*@author:闫克起2*@create:2022-11-2116:27**/@ComponentpublicclassAgeRoutePredicateFactoryextendsAbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config>{publicAgeRoutePredicateFactory(){super(AgeRoutePredicateFactory.Config.class);}@OverridepublicList<String>shortcutFieldOrder(){returnArrays.asList("minAge","maxAge");}@OverridepublicPredicate<ServerWebExchange>apply(Configconfig){return(serverWebExchange)->{ServerHttpRequestrequest=serverWebExchange.getRequest();//获取传递的年龄Stringage=request.getHeaders().getFirst("age");if(StringUtils.hasText(age)){inta=Integer.parseInt(age);if(a>=config.getMinAge()&&a<=config.getMaxAge()){returntrue;}}returnfalse;};}@ValidatedpublicstaticclassConfig{@NotNullprivateintminAge;@NotNullprivateintmaxAge;publicintgetMinAge(){returnminAge;}publicvoidsetMinAge(intminAge){this.minAge=minAge;}publicintgetMaxAge(){returnmaxAge;}publicvoidsetMaxAge(intmaxAge){this.maxAge=maxAge;}}}
小结:
gateway:网关,路由转发
ribbon:实现负载均衡
openfeign:完成服务之间的调用。
nacos:注册中心
6.2gateway中的过滤器
为请求到达微服务前可以添加相应的请求设置,响应后为响应结果添加一些设置。
gateway内部含有很多种过滤。
https://www.cnblogs.com/zhaoxiangjun/p/13042189.html
Tips:每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory结尾,这是SpringCloudGateway的一个约定,例如AddRequestHeader对应的实现类为AddRequestHeaderGatewayFilterFactory。
举例:StripPrefix用于截断原始请求的路径。
测试:
例子:设置响应的状态码2500
6.3自定义全局过滤器
例子:认证过滤。
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的认证校验。
开发中的鉴权逻辑:
当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
认证通过,将用户信息进行加密形成token[jwt],返回给客户端,作为登录凭证
以后每次请求,客户端都携带认证的token[携带请求头]
服务端对token进行解密,判断是否有效。
packagecom.aaa.filter;importcom.alibaba.fastjson.JSON;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.Ordered;importorg.springframework.core.io.buffer.DataBuffer;importorg.springframework.http.HttpStatus;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.http.server.reactive.ServerHttpResponse;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;importjava.nio.charset.StandardCharsets;importjava.util.HashMap;importjava.util.Map;/***@program:qy156-shop-parent*@description:*@author:闫克起2*@create:2022-11-2215:07**/@ComponentpublicclassLoginFilterimplementsGlobalFilter,Ordered{@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){ServerHttpRequestrequest=exchange.getRequest();ServerHttpResponseresponse=exchange.getResponse();//判断请求路径是否为放行。Stringpath=request.getPath().toString();if("/login".equals(path)){returnchain.filter(exchange);//放行}//获取请求头的token值。Stringtoken=request.getHeaders().getFirst("token");if(StringUtils.hasText(token)){//校验token是否有效if("admin".equals(token)){returnchain.filter(exchange);//放行}}//3.1设置状态码response.setStatusCode(HttpStatus.UNAUTHORIZED);//3.2封装返回数据Map<String,Object>map=newHashMap<>();map.put("msg","未登录");map.put("code","NOTLOGING");//3.3作JSON转换byte[]bytes=JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);//3.4调用bufferFactory方法,生成DataBuffer对象DataBufferbuffer=response.bufferFactory().wrap(bytes);//4.调用Mono中的just方法,返回要写给前端的JSON数据returnresponse.writeWith(Mono.just(buffer));}//优先级值越小优先级越高@OverridepublicintgetOrder(){return0;}}
7.统一跨域解决
第一种通过配置文件
spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowedOrigins:"*"allowedHeaders:"*"allowedMethods:"*"default-filters:-DedupeResponseHeader=VaryAccess-Control-Allow-OriginAccess-Control-Allow-Credentials,RETAIN_FIRST
第二种写一个配置类
importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.cors.CorsConfiguration;importorg.springframework.web.cors.reactive.CorsWebFilter;importorg.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;importorg.springframework.web.util.pattern.PathPatternParser;@ConfigurationpublicclassCorsConfig{@BeanpublicCorsWebFiltercorsFilter(){CorsConfigurationconfig=newCorsConfiguration();config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");UrlBasedCorsConfigurationSourcesource=newUrlBasedCorsConfigurationSource(newPathPatternParser());source.registerCorsConfiguration("/**",config);returnnewCorsWebFilter(source);}}