在我们使用springcloud框架时,所有的服务都被拆分成了一个一个的微服务,那么这些微服务的ip以及请求路径各不相同,对于前端人员来说,调用我们接口时的难度会加大,需要去专门记录每一个微服务的ip.这样不仅麻烦还容易出错.而且在认证方面,每一个微服务都会独立的去认证,这就很大的增加了业务的复杂性.在跨域问题上,处理起来也会很棘手.所以,网关的出现,就是顺应着sprigcloud架构中的这些问题而顺势孕育而生的一款产品,它的出现,可以帮我们统一的解决上述问题.
关于网关,它就是指系统的统一入口,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证,鉴权,监控(黑白名单),路由转发的功能等等.
Gateway网关的优缺点:
下面就详细说一下如何搭建Gateway网关
一.建立一个gateway微服务模块
二.在pom文件中引入gateway的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
三.编写配置文件
这里使用的是applicaiton.yml文件来进行编写
#gateway的端口号
server:
port: 7000
#服务应用名称
spring:
application:
name: springcloud-gateway
# 路由转发 List<RouteDefinition> routes
cloud:
gateway:
routes:
#list类型的数据
- id: lrs-product
# 路由转发的真实地址
uri: http://localhost:8081
# predicates:当满足断言时,才会转发到真实的uri地址。多个断言之间and的关系
predicates:
- Path=/product/**
- id: lrs-order
# http://localhost:8091/order/buy/1/100
uri: localhost:8091
predicates:
- Path=/order/**
四.添加主启动类
package com.lrs.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @作者:刘壬杉
* @创建时间 2022/8/20 10:20
**/
@SpringBootApplication
public class GatewayApp {
public static void main(String[] args) {
SpringApplication.run(GatewayApp.class,args);
}
}
五.运行该服务
我们看到,直接访问网关的ip就可以直接访问到目标接口上,不需要些接口所在服务的ip.
六.gateway升级版--配合nacos使用
我们通过上面的方式实现网关后,发现我们服务的ip是写死的,这样的话如果服务的ip变了,或者某个服务搭了集群,那我们总不可能通过写死ip的方式来实现网管功能了,所以这时候我们就应该想一下有没有什么东西可以让我动态的去访问某一类服务的ip地址----Nacos注册中心.我们一样可以通过网关的形式来拉取我们Nacos注册中心里注册过的服务,并且Nacos中包含Ribbon,可以帮我们实现服务集群的负载均衡,一举两得.
1.引入nacos的依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependncy>
2.修改配置文件
将uri修改为lb://+注册中心中的对应的微服务的名称,并配置注册中心
3.重启项目
访问成功,由此可见我们升级版已经配置成功了
七.gateway自动路由
通过我们上面的搭建方式,不难发现,每有一个微服务,那么就需要配置一个路由,那么假设项目中的服务有很多,我们一个一个去配置太麻烦了,这时候我们就可以通过gateway自动配置路由.
1.修改配置文件
2.重启gateway服务
注意:这时我们的访问路径变了!!
通过自动路由,我们访问时,需要拼接上注册中心中已经注册的服务的服务名称 再加具体的访问路径才能成功访问对应的接口.
八.介绍gateway中的断言
1.内置的断言器
(1)基于DateTime类型的断言工厂
① AfterRoutePredicateFactory 接收一个日期参数,判断请求日期是否晚于指定日期
- After=2022-01-01T23:59:59.789+08:00[Asia/Shanghai]
② BeforeRoutePredicateFactory 接收一个日期参数,判断请求日期是否早于指定日期
- Before=2022-01-01T23:59:59.789+08:00[Asia/Shanghai]
③ BetweenRoutePredicateFactory 接收两个日期参数,判断请求日期是否在指定时间段内
- Between=2020-02-13T18:27:28.309+08:00[Asia/Shanghai],2025-02-13T18:27:28.309+08:00[Asia/Shanghai]
(2)基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory
- RemoteAddr=192.168.1.1,192.168.1.2,127.0.0.1,.....
(3)基于cookie的断言工厂 CookieRoutePredicateFactory
当我们的请求中包含了Cookie name=XXX value=zzz的时候才会转发请求
- Cookie=XXX,zzz
(4)基于Header的断言工厂 HeaderRoutePredicateFactory
当请求中的Header包含key=XXX,value=zzz的时候才会转发请求
- Header=XXX,zzz
(5)基于Host的断言工厂 HostRoutePredicateFactory
Host必须满足www.lrs.com:7000或者localhost:7000才会转发请求
- Host=www.lrs.com:7000,localhost:7000
(6)基于Method请求方法的断言工厂 MethodRoutePredicateFactory
只有满足规定的提交方式的要求才能转发请求
- Method=Post
***(7)基于Path请求路径的断言工厂 PathRoutePredicateFactory
- Path=/order/**
(8)基于Query请求参数的断言工厂 QueryRoutePredicateFactory
- Query=baz, ba.
(9)基于路由权重的断言工厂 WeightRoutePredicateFactory
2.自定义断言器
要求:名称必须为XXXRoutePredicateFactory并且继承 AbstractRoutePredicateFactory
使用时:XXX=
例子:我们定义一个断言为:Age满足一定范围才能转发请求
(1)创建一个AgeRoutePredicateFactory类
package com.lrs.gateway.config;
import com.alibaba.nacos.common.utils.StringUtils;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @作者:刘壬杉
* @创建时间 2022/8/21 18:46
**/
//泛型为自定义的一个配置类,该配置类用来存放配置文件中的值
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory() {
super(AgeRoutePredicateFactory.Config.class);
}
//读取配置文件中的内容并配置给配置类中的属性
public List<String> shortcutFieldOrder() {
return Arrays.asList("minAge","maxAge");
}
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
return (exchange) -> {
String age = exchange.getRequest().getQueryParams().getFirst("age");
if(StringUtils.isNotEmpty(age)){
int a = Integer.parseInt(age);
return a>=config.minAge && a<=config.maxAge;
}
return true;
};
}
@Data
@NoArgsConstructor
public static class Config{
private Integer minAge;
private Integer maxAge;
}
}
(2)在application.yml文件中添加age的配置
(3)重启服务并测试
满足age为18~20的条件时:
不满足时:
九.gateway中的过滤器
在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。
- GatewayFilter:应用到单个路由或者一个分组的路由上。
- GlobalFilter:应用到所有的路由上。
1.局部过滤器
(1)内置过滤器
局部过滤器中内置了很多不同类型的网关路由过滤器。具体可以参考下面的连接:
Spring Cloud Gateway 内置的过滤器工厂 - 小菜鸟攻城狮 - 博客园
![](https://img-blog.csdnimg.cn/7cb539ff0c6744878f6937840f419507.png)
(2)自定义过滤器
例子:配置一个Log过滤器,并要求传递两个布尔值.当第一个值为true时,开启控制台日志,第二个值为true时,开启缓存日志
①在配置文件中,添加一个Log的过滤器配置
②自定义一个过滤器工厂,实现方法
package com.lrs.gateway.config;
import lombok.Data;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog","cacheLog");
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if(config.cacheLog){
System.out.println("开启缓存日志");
}
if(config.consoleLog){
System.out.println("开启控制台日志");
}
return chain.filter(exchange);
}
};
}
@Data
public static class Config {
private Boolean consoleLog;
private Boolean cacheLog;
public Config() {
}
}
}
③重启服务,测试:
2.全局过滤器
全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。
(1)内置全局过滤器
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
(2)自定义全局过滤器
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
开发中的鉴权逻辑:
- 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
- 认证通过,将用户信息进行加密形成token,返回给客户端aaaa,作为登录凭证
- 以后每次请求,客户端都携带认证的token
- 服务端对token进行解密,判断是否有效。
如上图,对于验证用户是否已经登录鉴权的过程可以在网关统一检验。
检验的标准就是请求中是否携带token凭证以及token的正确性。
下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑
自定义全局过滤器 要求:必须实现GlobalFilter,Ordered接口
https://my.oschina.net/tongyufu/blog/2120317
①新建一个LoginFilter类并交予容器管理
package com.lrs.gateway.fliter;
import com.alibaba.nacos.common.utils.StringUtils;
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;
/**
* @作者:刘壬杉
* @创建时间 2022/8/21 19:27
**/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(StringUtils.isNotEmpty(token) && StringUtils.equals(token,"admin")){
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//优先级 值越小优先级越高
@Override
public int getOrder() {
return 1;
}
}
②创建一个Anon类,接收配置文件中的参数
package com.lrs.gateway.entity;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "anon")
public class Anon {
private List<String> url;
public Anon() {
}
public Anon(List<String> url) {
this.url = url;
}
public List<String> getUrl() {
return url;
}
public void setUrl(List<String> url) {
this.url = url;
}
}
③追加配置文件内容
④在product的controller层追加一个接口做测试
@GetMapping("getMsg")
public String getMsg(){
return "aaaaaaaaaaaaaaaaaaaaa";
}
⑤重启服务并测试
被放行的路径正常访问:
没有放行的路径需要加上token且值为admin才能访问: