实际项目中存在这样一个场景,使用Spring Cloud Gateway建设一个网关,该网关负责代理所有业务系统的对外访问请求。若外部服务需要授权(下文中的passID和passToken)才能访问,那么该网关可以统一处理该问题。
以下引出添加AddRequestHeader的几种方式,以及存在的限制。
采用yaml方式配置 ,访问csdn网站,X-Request-red = blue,该方式适用于已知数据的添加。
spring:
application:
name: gateway-application
cloud:
# Spring Cloud Gateway 配置项,对应 GatewayProperties 类
gateway:
# 路由配置项,对应 RouteDefinition 数组
routes:
- id: csdn1 # 路由的编号
uri: https://blog.csdn.net # 路由到的目标地址,只能到域名部分 https://blog.csdn.net/dear_little_bear
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/csdn1
filters:
- StripPrefix=1
- AddRequestHeader=X-Request-red, blue
采用RouteLocator编程方式 ,访问csdn网站,该方式适用于不变的数据(“单例”)的添加。
@Configuration
public class RouteConfiguration {
@Value("${gateway.csdn.host}")
private String csdnHost;
@Value("${gateway.csdn.PaaSID}")
private String paaSID;
@Value("${gateway.csdn.PaaSToken}")
private String paaSToken;
@Value("${gateway.csdn.nonce}")
private String nonce;
@Autowired
private CsdnRequestFilter csdnRequestFilter;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) throws UnsupportedEncodingException {
String timeTamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = SecureUtil.sha256(timeTamp + nonce + paaSToken);
return builder.routes()
.route(r -> r.path("/csdn2/**")
.filters(f ->
f.stripPrefix(1)
.addRequestHeader("x-tif-nonce", nonce)
.addRequestHeader("x-tif-signature", signature)
.addRequestHeader("x-tif-paasid", paaSID)
.addRequestHeader("x-tif-timestamp", timeTamp))
.uri(csdnHost)
.order(2))
/*.route(r -> r.path("/csdn3/**")
.filters(f->f.stripPrefix(1)
.filter(csdnRequestFilter))
.uri(csdnHost)
.order(3))*/
.build();
}
}
2020-04-04 14:16:15.141 DEBUG 10204 — [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping : Mapping [Exchange: GET http://localhost:8888/csdn2] to Route{id=‘c3c865da-0fc2-47e3-aef2-55864afd626f’, uri=https://blog.csdn.net:443, order=2, predicate=Paths: [/csdn2/**], match trailing slash: true, gatewayFilters=[[[StripPrefix parts = 1], order = 0], [[AddRequestHeader x-tif-nonce = ‘123456789abcdefg’], order = 0], [[AddRequestHeader x-tif-signature = ‘3dee7471fe6b71a2b66f3b187c8cca844c3aef47afa4020f7df3b5c386015955’], order = 0], [[AddRequestHeader x-tif-paasid = ‘csdndemo’], order = 0], [[AddRequestHeader x-tif-timestamp = ‘1585980879’], order = 0]], metadata={}}
2020-04-04 14:20:14.343 DEBUG 10204 — [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping : Mapping [Exchange: GET http://localhost:8888/csdn2] to Route{id=‘c3c865da-0fc2-47e3-aef2-55864afd626f’, uri=https://blog.csdn.net:443, order=2, predicate=Paths: [/csdn2/**], match trailing slash: true, gatewayFilters=[[[StripPrefix parts = 1], order = 0], [[AddRequestHeader x-tif-nonce = ‘123456789abcdefg’], order = 0], [[AddRequestHeader x-tif-signature = ‘3dee7471fe6b71a2b66f3b187c8cca844c3aef47afa4020f7df3b5c386015955’], order = 0], [[AddRequestHeader x-tif-paasid = ‘csdndemo’], order = 0], [[AddRequestHeader x-tif-timestamp = ‘1585980879’], order = 0]], metadata={}}
我们发现timestamp和signature都没有发生变化,即便在customRouteLocator上加上Scope,依然无效。查看源码后发现,当我们启动了Spring Cloud Gateway之后,该RouteLocator就会建立,并且不会重复new。但这不是我们想要的结果。
(1) 自定义一个CsdnRequestFilter实现GatewayFilter,CsdnRequestFilter 代码如下:
@Component
public class CsdnRequestFilter implements GatewayFilter, Ordered {
@Value("${gateway.csdn.PaaSID}")
private String paaSID;
@Value("${gateway.csdn.PaaSToken}")
private String paaSToken;
@Value("${gateway.csdn.nonce}")
private String nonce;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String timeTamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = SecureUtil.sha256(timeTamp + nonce + paaSToken);
System.out.print("自定义过滤器:timeTamp:"+timeTamp+";signature:"+signature);
//提取应用账户及应用令牌,鉴权
ServerHttpRequest request = exchange.getRequest().mutate()
.header("x-tif-nonce", nonce)
.header("x-tif-signature", signature)
.header("x-tif-paasid", paaSID)
.header("x-tif-timestamp", timeTamp)
.build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return 1;
}
}
(2) 在RouteLocator类中添加该gatewayfilter,代码如下:
return builder.routes()
.route(r -> r.path("/csdn3/**")
.filters(f->f.stripPrefix(1)
.filter(csdnRequestFilter))
.uri(csdnHost)
.order(3))
.build();
两次请求结果如下:
自定义过滤器:timeTamp:1585982693;signature:110ad2c949fb9e9afe2c8f98a81f6f445fd85ad43e0dec1ab555dae8f06b8f62 2020-04-04 14:44:53.180
自定义过滤器:timeTamp:1585980894;signature:265ff5cbac42c99969403285b897c24e2db59151f456a842669c7e7726fb9c23 2020-04-04 14:14:54.504
发现timeTamp和signature都发生了改变。原因是自定义gatewayfilter会在每次http请求过程中,动态创建。
特别注意
header是readonly类型,所以在CsdnRequestFilter中新增header,采用的是exchange.getRequest().mutate(),该方法将会构造出一个新的ServerHttpRequest。同时,也使用了exchange.mutate() 方法重新构造了一个ServerWebExchange 。
以上希望对你有帮助。
源码: https://github.com/muziye2013/SpringBoot-Labs 参照 labx-08/labx-08-ex-gateway-demo01章节