Spring Cloud Gateway关于body
前言
当前需求:
- 仅读取--客户端传送数据的content_type: application/json, post请求,服务器需要取出body进行某些字段验证.
- 读取并修改---客户端传送数据的content_type: application/json, post请求,服务器需要取出body进行修改并传入下级.
1. 仅读取 判断是否拦截
pom.xml
使用的应该是挺新的版本了
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mydemo</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<version>2.2.3.RELEASE</version>
</dependency>
</dependencies>
router
这里仅定义一个就行, 流的顺序不要变了, path filter放在最后面
猜测: 根据链式来走的,readybody已经调用了,但是下面的path 之类的的不匹配,就不往下走了
其他的router可以放在yml 正常即可
如果get请求也有body的话.. 就看一下代码中的注释
/**
* @author kun.han
*/
@EnableAutoConfiguration
@Component
public class ApiLocator {
@Resource
private RequestFilter requestFilter;
public static Logger LOGGER = LoggerFactory.getLogger(ApiLocator.class);
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("love",
r -> r
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.or()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
// 如果get请求也有body的话.. 就把下面两行注释掉
.and()
.method(HttpMethod.POST)
.and()
.readBody(String.class, readBody -> {
LOGGER.info("request method POST, body is:{}", readBody);
return true;
})
.and()
.path("/一个项目用不到的路径/**")
.filters(f -> {
f.stripPrefix(1);
return f;
})
.uri("http://localhost:10005"));
RouteLocator routeLocator = serviceProvider.build();
System.out.println("custom RouteLocator is loading ... {}" + routeLocator);
return routeLocator;
}
}
filter
/**
* 全局过滤器
*
* @author kun.han
*/
@Component
public class AuthorizeFilterNew implements GlobalFilter, Ordered {
public static Logger LOGGER = LoggerFactory.getLogger(AuthorizeFilterNew.class);
private static void run() {
LOGGER.info("请求结束");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 某些路径不作验证
String path = request.getPath().toString();
if (path.contains("/anon/")){
return chain.filter(exchange);
}
// 试图从header获取关键验证参数
HttpHeaders headers = request.getHeaders();
String token = headers.getFirst(ConstantMsg.AUTHORIZE_TOKEN);
if (StringUtils.isEmpty(token)) {
// 试图从params获取关键验证参数
token = request.getQueryParams().getFirst(ConstantMsg.AUTHORIZE_TOKEN);
}
// 试图从xxx-www-f 和 application/json body 获取关键验证参数
String requestBody = exchange.getAttribute("cachedRequestBodyObject");
LOGGER.info("requestBody : {}", requestBody);
if (!StringUtils.hasText(token) && Objects.nonNull(requestBody)) {
MediaType mediaType = request.getHeaders().getContentType();
// 从body中获取想要的数据 自定义的方法 在utils
token = AuthUtils.getTokenByMediaType(requestBody, mediaType);
}
// 获取到验证参数 进行验证
if (AuthUtils.authTokenNormal(token)) {
return chain.filter(exchange).then(Mono.fromRunnable(AuthorizeFilterNew::run));
} else {
return AuthUtils.authFailure(exchange);
}
}
@Override
public int getOrder() {
return 0;
}
}
utils
整理xxx-form格式数据 / 返回application/json格式信息
/**
* @author kun.han on 2020/6/5 13:05
*/
public class AuthUtils {
public static Mono<Void> authFailure(ServerWebExchange exchange) {
// 验证失败状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 返回json格式错误信息
// 自定义的一个返回bean 可自己定义
BaseAO resultInfo = JsonResult.authFailureMap(ConstantMsg.PARAM_IS_NULL);
String resultInfoString = JSONObject.toJSONString(resultInfo);
// 主要是这一步
byte[] bytes = resultInfoString.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
public static Boolean authTokenNormal(String token){
String dataToken = "test_token";
return !StringUtils.isEmpty(token) && dataToken.equals(token);
}
/**
* Decode body map.
* xxx-www-f
*
* @param body the body
* @return the map
*/
public static JSONObject decodeBody(String body) {
Map<String, String> map = Arrays.stream(body.split("&"))
.map(s -> s.split("="))
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
return JSONObject.parseObject(JSONObject.toJSONString(map));
}
/**
* Gets token by media type.
* 从body中获取想要的数据
*
* @param requestBody the request body
* @param mediaType the media type
* @return the token by media type
*/
public static String getTokenByMediaType(String requestBody, MediaType mediaType) {
String token;
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
JSONObject jsonObject = AuthUtils.decodeBody(requestBody);
token = jsonObject.getString(ConstantMsg.AUTHORIZE_TOKEN);
}else {
JSONObject jsonObject = JSONObject.parseObject(requestBody);
token = jsonObject.getString(ConstantMsg.AUTHORIZE_TOKEN);
}
return token;
}
}
2. 读取并修改 传入下级
filter
这里仅写一下全局filter demo
如果仅对某些路径进行修改body 可以利用request.getPath()进行判断是否需要, 或者其他自定义过滤器 (implements GatewayFilter, Ordered)
请求方式为get的也可以 把判断去掉或者自己改一下
/**
* token校验全局过滤器
* @author kun.han
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
private static final String AUTHORIZE_TOKEN = "token";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("AuthorizeFilter");
Object requestBody = exchange.getAttribute("cachedRequestBodyObject");
System.out.println(JSONObject.toJSONString(requestBody));
ServerHttpRequest request = exchange.getRequest();
// 某些路径不作验证
String path = request.getPath().toString();
if (path.contains("/lov/")){
return chain.filter(exchange);
}
if (HttpMethod.POST.equals(exchange.getRequest().getMethod())){
// 重新构造request
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
// 重点
Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
// APPLICATION_JSON
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
// body为string
JSONObject jsonObject = JSONObject.parseObject(body);
String newBody = "对body进行处理之后的新body";
return Mono.just(newBody);
}else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
// xxx-form- 类型的数据解析成map
Map<String, Object> bodyMap = decodeBody(body);
// 对bodyMap进行处理之后的newBodyMap
Map<String, Object> newBodyMap = bodyMap ;
// 通过自定义工具重新组装 xxx-form-
String encodeBody = encodeBody(newBodyMap);
return Mono.just(encodeBody);
}
return Mono.empty();
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(exchange.getRequest().getHeaders());
// 先删除 content length
httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, httpHeaders);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequest decorator = this.decorate(exchange, httpHeaders, outputMessage);
return chain.filter(exchange.mutate().request(decorator).build());
}));
} else {
return chain.filter(exchange);
}
}
@Override
public int getOrder() {
return 0;
}
ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0L) {
// 重新写 content length
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
/**
* Decode body map.
* xxx-www-f
*
* @param body the body
* @return the map
*/
private Map<String, Object> decodeBody(String body) {
return Arrays.stream(body.split("&"))
.map(s -> s.split("="))
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
/**
* encode body map.
* xxx-www-f
*
* @param body the body
* @return the map
*/
private String encodeBody(Map<String, Object> map) {
return map.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
}
}
这种方式就只更改,传入下级
如果要对body判断匹配之类的,然后返回什么鉴权失败的话,用utils中的authFailure也可以,但是用一个问题,就是客户端可以正常接收返回数据,但是服务器会报一个异常的错误...个人不喜欢这个一大串的报错信息,所以个人是要么只读,然后做判断,要么只改,然后传入下级.
视个人而定吧.
后话
这些也是在google找的东西, 然后进行整理拿出自己需要用到的...