简介
Spring Cloud Gateway旨在提供一种简单而有效的方式来路由到API,并为它们提供跨领域的关注点,例如:安全性、监控/度量和弹性。
Spring Cloud Gateway构建在Spring Boot 2.x、 Spring WebFlux和Project Reactor。因此,当您使用Spring Cloud Gateway时,许多您熟悉库(例如Spring Data and Spring Security)和模式可能不需使用。
Spring Cloud Gateway需要Spring Boot和Spring Webflux提供的Netty运行时。它不能在传统的Servlet容器中工作,也不能在构建为WAR时工作。
概念
Route(路由): 网关的基本构造块。它由一个ID、一个目标URI、一组断言和一组过滤器定义。如果断言为true,则匹配路由。
Predicate(断言): 一个Java8功能断言。 输出类型是Spring Framework ServerWebExchange。 你可以匹配任何来HTTP请求, 例如headers和参数.
Filter(过滤器):这里有很多factory构建GatewayFilter类。你可以在发送到下游请求之前或者之后修改请求和返回。
如何工作
下图对Spring Cloud Gateway工作进行了一个概述:
Clients向Spring Cloud Gateway发出请求。如果Gateway Handler Mapping检测到一个请求匹配路由,请求将被发送给Gateway Web Handler。Handler通过Filter处理请求。Filter被虚线分隔的原因是,Filter可以在代理请求发送之前和之后运行。先执行发送之前处理,然后Proxy Filter再运行,最后执行发送之后的处理。
配置Predicate Factories和Filter Factories
有两种方法可以配置Predicate和Filter快捷方式和完全参数。下面的大多数示例都使用快捷方式。
完整参数
完整参数更显是一个标准的yaml配置包含名称和值。通常,有name键和args键。args键是键-值的map配置Predicate和Filter。
application.yml
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
快捷方式
快捷方式配置由Predicate名称、等号(=)和逗号(,)分隔的参数值识别。
application.yml
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
RoutePredicateFactory
RoutePredicateFactories包含了11种配置,具体怎么配置不一一说明了,这里只讲述下支持那些配置。
GatewayFilterFactory
支持23种方式,由于太多,将不一一列举。
由于篇幅原因,RoutePredicateFactory和GatewayFilterFactory使用方式未一一列举,这里
链接,方便用到是查看样例。
快速使用
创建名称为gateway的module,pom.xml配置:
<!--引入nacos client的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- gateway 网关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml配置
server:
port: 7007
spring:
application:
name: GATEWAY
cloud:
nacos:
discovery:
namespace: ec6004af-f122-4ed1-b6d6-5d77ea5d5c94
server-addr: 192.168.43.85:8845,192.168.43.229:8846,192.168.43.251:8847
gateway:
routes:
- id: myRoute
uri: lb://USERS
predicates:
- Path=/service-order/**
filters:
- StripPrefix=1
uri中使用lb://USERS,其中lb表示使用nacos中心注册的服务名,这样就不需知道微服务的地址和端口,只需知道其他注册到nacos的服务名称就可以了。
path对service-order路径下所有请求进行拦截访问,访问名称为USERS的微服务。即http://USERS/service-order/**。
StripPrefix表示去掉一程路径,即变成了http://USERS/**。
简单的说就是方位http://网关:7007//service-order/**,最后被反射代理去访问了http://USERS/**目标地址。其中USERS是对应微服务的访问IP+端口。
问题
这里有一个问题,也是常见的问题,就是我们所有的服务都可以访问网关,也可以直接访问目标微服务,那如何屏蔽用户直接访问目标微服务呢?
有两种办法,
1、服务器层次更改,直接屏蔽其他微服务端口,对外不提供方位,只应许内部访问,对外只提供网关端口。(推荐使用这种方式)
2、代码层次,使用网关,对所有经过网关请求进行拦截,请求头添加参数,每个微服务进度请求进行拦截,去解析请求是否存在该参数,无这是为非法入侵请求。
网关拦截代码
package com.juwusheng.gateway;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author lhq
* @date 2022年04月07日 下午5:37
*/
@Configuration
public class TokenFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* 可增加令牌等相关信息进行校验
* 这里创建一个令牌,设置有效期,缓存到redis数据库上,
* 实际调用的微服务端去redis查看是否有相关令牌
*/
ServerHttpRequest req = exchange.getRequest().mutate()
.header("from", "gateway").build();
return chain.filter(exchange.mutate().request(req.mutate().build()).build());
}
}
每个微服务对应的拦截
package com.juwusheng.products;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author lhq
* @date 2022年04月07日 下午5:59
*/
@EnableWebMvc
@Configuration
public class InterceptorConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new GlobalInterceptor());
}
}
package com.juwusheng.products;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @author lhq
* @date 2022年04月07日 下午5:43
*/
@Component
public class GlobalInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String secretKey = request.getHeader("from");
if(StringUtils.hasText(secretKey)||secretKey.equals("gateway")){
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("error");
return false;
}
return true;
}
}