目录
1、什么是网关
网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,所以,使用网关的好处在于:
(1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;
(2)降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性
(3)解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑
Nginx 和 Gateway 的区别:
Nginx 是服务器级别的,将请求转发到gateway(负载均衡)
Nginx 是用户到前端工程的网关,对外网关
gateway是项目级别的,进行路由转发
Gateway是微服务网关,是前端工程到后台服务之间的一个对内网关
2、断言和GateWay过滤器
网关请求流图:
2.1、什么是断言?
表示如果访问/doLogin就会转发到/login-service上;-After代表必须在2022-03-22 9:42:59 以后才能进行转发,在此之前访问/doLogin是不会进行转发的;Method代表该请求必须是GET或者Post请求才可以。三者都满足时才能进行访问。
Spring提供了11种基本的Predicate工厂:
2.2、GateWay过滤器
gateway 里面的过滤器和 Servlet 里面的过滤器,功能差不多,路由过滤器可以用于修改进入
Gateway网关的过滤器分为两种,一种是局部过滤器,一种是全局过滤器。
过滤器,顾名思义,就是过滤一些请求,在这里,局部过滤器需要配置某个路由,才能过滤(可以给某个路径单独做处理)。全局过滤器的作用是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
- GatewayFilter:应用到单个路由或者一个分组的路由上。
- GlobalFilter:应用到所有的路由上。
2.3、过滤器执行顺序
每个过滤器都会被执行两次,过滤分为pre和post。
pre:请求前调用。
post:响应结果返回时调用,顺序和pre完全相反。
请求进入网关会碰到三类过滤器:
路由的过滤器:是针对特定路由的过滤器
DefaultFilter:DefaultFilter(默认过滤器)是预定义的全局过滤器,提供了一些常用功能的实
GlobalFilter:是应用于所有路由的过滤器。
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, 路由过滤器,只针对当前路由生效 # 添加请求头
default-filters: # 默认过滤项
- AddRequestHeader=Truth, 默认过滤器,所有路由生效!
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序规则如下:
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定。路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
2.4、使用GatewayFilter进行ip拦截
前端发起请求---->过滤器拦截获取访问ip(request.getHeaders().gethost().getHostString())--->去数据库中查询黑名单的ip集合--->list.contions(ip)---->判断访问的ip是否为黑名单里的--->放行/拦截
如果并发量大的情况,所有的请求都去数据库中查询黑名单,这样的话数据库的压力较大。可以把一些指定的黑名单ip直接写死,写为常量集合。这样可以先判断常量集合中是否存才,没有再去数据库中查询,这里也可以使用redis缓存黑名单ip来减轻数据库的压力。
2.5、使用Gateway做token校验
使用GateWay做统一用户认证,使用的是jwt生成的token。用户发起登录请求到网关,请求头中携带token,网关的全局过滤器进行拦截,校验token。
/**
*
* 全局过滤器
* @author :Rk.
* @date : 2022/11/23
*/
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
/**
* 拦截请求 进行过滤
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
log.info("==="+path);
//内部服务接口,不允许外部访问 如果是以下 直接拦截
if(antPathMatcher.match("/**/inner/**", path)) {
ServerHttpResponse response = exchange.getResponse();
return out(response, ResultCodeEnum.PERMISSION);
}
Long userId = this.getUserId(request);
//api接口,异步请求,校验用户必须登录
if(antPathMatcher.match("/api/**/auth/**", path)) {
//如果userid为空 则认证失败 说明用户未登录 进行拦截
if(StringUtils.isEmpty(userId)) {
ServerHttpResponse response = exchange.getResponse();
return out(response, ResultCodeEnum.LOGIN_AUTH);
}
}
//继续向下(优先级更低的过滤器)执行
return chain.filter(exchange);
}
/**
* 过滤器优先级 返回值越小,执行优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
/**
* 接口鉴权失败返回数据
* @param response
* @param resultCodeEnum
* @return
*/
private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
Result result = Result.build(null, resultCodeEnum);
byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
/**
* 获取当前登录用户id
* @param request
* @return
*/
private Long getUserId(ServerHttpRequest request) {
String token = "";
//获取请求头中的token
List<String> tokenList = request.getHeaders().get("token");
if(null != tokenList) {
token = tokenList.get(0);
}
//如果token不为空
if(!StringUtils.isEmpty(token)) {
//校验token并获取UserId
return JwtHelper.getUserId(token);
}
return null;
}
}
2.6、使用GateWay实现限流
什么是限流?
限流模型:限流模型:漏斗算法 ,令牌桶算法,窗口滑动算法 计数器算法

/**
* 自定义请求限制的
*/
@Configuration
public class RequestLimitConfig {
//ip限流
// 针对某一个接口 ip来限流 /doLogin 每一个ip 10s只能访问3次
@Bean
@Primary // 主候选的
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
}
//把请求路径作为限流
// 针对这个路径来限制 /doLogin
// api 就是 接口 外面一般把gateway api网关 新一代网关
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
/*** 用户 id 限流
* 把用户 ID 作为限流的 key
** @return */
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
}
下面是一个秒杀的案例:
(秒杀项目) 4.9 削峰限流与防刷(核心)_妙先森的博客-优快云博客_削峰和限流
2.7、GateWay的跨域配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
方法二:配置文件
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]'
allowCredentials: true
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期