Gateway
创建网关
官网:https://docs.spring.io/spring-cloud-gateway
网关并不属于业务,在创建工程时最好放在与model层同级目录下
使用时,需要引入spring-cloud-starter-gateway
,并且由于网关的工作目的是将请求根据不同uri路由到不同的微服务中,所以还需要nacos注册中心来知道所有微服务的地址spring-cloud-starter-alibaba-nacos-discovery
,在配置文件中也需要配置配置中心的地址。
pom.xml
<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-gateway</artifactId>
</dependency>
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848
# http默认访问80端口,之后访问网关可以不写端口
server:
port: 80
@SpringBootApplication
public class GatewayMainApplication{
public static void main(String[] args){
SpringApplication.run(GatewayMainApplication.class,args);
}
}
路由
spring:
cloud:
gateway:
routes:
- id: order-route
uri: lb://service-order
predicates:
- Path=/api/order/**
- id: product-route
uri: lb://service-product
predicates:
- Path=/api/product/**
# 效果:访问localhost/api/order/1时,请求会被路由到service-order服务中的某一个服务器,并且是负载均衡的
配置路由规则,这里的id用以配置路由名,uri表示路由到的服务(这里的lb表示以负载均衡的方式进行路由),predicates表示路由需要满足的条件
这里由于使用了nacos的负载均衡,故要引入负载均衡场景spring-cloud-starter-loadbalancer
在spring-gateway中,默认情况下路由匹配是根据所配置的路由从上到下进行的,但同时也可以为添加的路由指定order
属性来定义匹配顺序(数字越小优先级越高)
断言
即满足路由匹配的规则,在配置文件中用predicates:
属性来进行描述,需要传入一个数组.
断言的匹配规则可以是时间、cookie、请求头、host、请求方法、uri等,具体可以参照官方文档https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html
自定义断言工厂
除开使用spring提供的那些断言工厂外,还可以自定义断言规则
例如,我需要自定义一个判断token是否为空的断言工厂
路由配置文件
predicates:
- TokenNotEmpty=true
// 这里的类名必须是断言规则名加上RoutePredicateFactory
// 实际上,gateway默认的几种断言规则就是以这种形式进行命名的
// AbstractRoutePredicateFactory这个是抽象的断言工厂,所有的断言工厂都必须继承该类
// 这里传入了一个泛型,该泛型是一个内部类,用于定义断言规则,自定义断言工厂时,可以直接copy一个config然后自行修改
@Component // 声明为Spring组件,使其能被自动扫描
public class TokenNotEmptyRoutePredicateFactory
extends AbstractRoutePredicateFactory<TokenNotEmptyRoutePredicateFactory.Config> {
// 默认的token查询参数名
private static final String TOKEN_KEY = "token";
// 默认的授权头字段名
private static final String HEADER_KEY = "Authorization";
// Bearer Token的前缀
private static final String BEARER_PREFIX = "Bearer ";
/**
* 构造函数,必须调用父类的构造函数并传入配置类
*/
public TokenNotEmptyRoutePredicateFactory() {
super(Config.class);
}
/**
* 定义配置参数的快捷字段顺序
* 用于YAML/Properties配置时的简写形式
* @return 配置参数的字段名列表
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("required");
}
/**
* 创建实际的断言逻辑
* @param config 配置对象
* @return 断言谓词
*/
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
/**
* 测试方法,判断当前请求是否匹配该路由
* @param exchange 服务器网络交换对象,包含请求/响应信息
* @return 是否匹配路由
*/
@Override
public boolean test(ServerWebExchange exchange) {
// 1. 首先检查请求头中的Authorization字段
String authHeader = exchange.getRequest().getHeaders().getFirst(HEADER_KEY);
// 检查Authorization头是否存在且以Bearer开头
if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
// 提取Bearer后面的token部分
String token = authHeader.substring(BEARER_PREFIX.length());
if (StringUtils.hasText(token)) {
return true; // 有效的Bearer Token
}
}
// 2. 检查查询参数中的token字段
String tokenParam = exchange.getRequest().getQueryParams().getFirst(TOKEN_KEY);
if (StringUtils.hasText(tokenParam)) {
return true; // 有效的查询参数token
}
// 3. 如果以上都不满足,根据配置决定是否允许通过
// required=true表示必须要有token,此时返回false
// required=false表示不强制要求token,此时返回true
return !config.isRequired();
}
/**
* toString方法,用于日志和调试
* @return 断言的字符串表示
*/
@Override
public String toString() {
return String.format("TokenNotEmpty: required=%s", config.isRequired());
}
};
}
/**
* 配置类,用于接收YAML/Properties中的配置参数
*/
public static class Config {
// 是否必须包含token,默认为true
private boolean required = true;
/**
* 获取required配置值
* @return 是否必须包含token
*/
public boolean isRequired() {
return required;
}
/**
* 设置required配置值
* @param required 是否必须包含token
*/
public void setRequired(boolean required) {
this.required = required;
}
}
}
过滤器
概述
该处的过滤器和springmvc中的过滤器非常类似,请求先按照顺序经过前置过滤器到达目的地,之后再按照逆序经过后置过滤器最后响应给请求方
SpringCloudGateway中内置了大量的过滤器工厂https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html
例如我想要将请求路径的/api去除掉
spring:
cloud:
gateway:
filters:
- RewritePath=/api/?(?<segment>.*), /$\{segment}
默认过滤器
默认过滤器(Default Filters)
是在全局路由配置中定义的过滤器,会应用到所有路由,而路由级别的filters支队特定路由生效。默认情况下default-filters优先级高于GlobalFilter(默认过滤器优先级通常为0)
spring:
cloud:
gateway:
default-filters:
- AddRequestParameter=default_param, 123
全局过滤器
全局过滤器(Global Filters)
通过实现GlobalFilter接口编写的过滤器,强制对所有请求生效
通常用于跨切面逻辑
例子:拦截所有未携带token的请求
@Component
@Order(-1) // 控制执行顺序(数字越小,优先级越高,default-filters优先级通常为0)
public class AuthGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 检查请求头是否有 Token
if (!exchange.getRequest().getHeaders().containsKey("Authorization")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 拦截请求
}
return chain.filter(exchange); // 放行
}
}
自定义过滤器工厂
@Component
public class OneTimeTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<OneTimeTokenGatewayFilterFactory.Config> {
// 存储有效令牌的缓存
private static final Map<String, String> tokenCache = new ConcurrentHashMap<>();
public OneTimeTokenGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("enabled");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (!config.isEnabled()) {
return chain.filter(exchange);
}
// 从请求头获取令牌
String token = exchange.getRequest().getHeaders().getFirst("X-One-Time-Token");
if (!StringUtils.hasText(token)) {
return unauthorizedResponse(exchange, "Missing one-time token");
}
// 验证令牌是否存在
if (!tokenCache.containsKey(token)) {
return unauthorizedResponse(exchange, "Invalid or expired one-time token");
}
// 移除已使用的令牌(一次性)
tokenCache.remove(token);
return chain.filter(exchange);
};
}
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 生成一次性令牌的公共方法(可由其他服务调用)
public static String generateToken(String value) {
String token = java.util.UUID.randomUUID().toString();
tokenCache.put(token, value);
return token;
}
// 验证令牌的公共方法
public static boolean validateToken(String token) {
return tokenCache.containsKey(token);
}
public static class Config {
private boolean enabled = false;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
spring:
cloud:
gateway:
routes:
- id: secure-service
uri: http://example.com
predicates:
- Path=/api/secure/**
filters:
- name: OneTimeToken
args:
enabled: true
过滤器配置跨域
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "https://"
alloweMethods:
- GET