Spring Cloud Gateway在过滤器中获取请求body
苗锦洲
0
27
178
0
对于一些需要验证码的接口,可以设置过滤器,统一获取请求体中的验证码相关信息进行验证
友情提示:此篇文章大约需要阅读 27分46秒
# 最新解决方式
重点:使用DataBufferUtils备份请求body
完整代码:
```java
package tech.ordinaryroad.gateway.flter;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import tech.ordinaryroad.commons.core.base.result.Result;
import tech.ordinaryroad.commons.core.utils.server.ServletUtils;
import tech.ordinaryroad.gateway.service.ICaptchaService;
import java.nio.charset.StandardCharsets;
/**
* 验证码过滤器
*
* @author ruoyi
*/
@RequiredArgsConstructor
@Order
@Component
public class ValidateCodeFilter implements WebFilter {
private static final String[] VALIDATE_URL = new String[]{"/login", "/upms/user/register"};
private static final String OR_NUMBER = "orNumber";
private static final String EMAIL = "email";
private static final String CODE = "code";
private final ICaptchaService captchaService;
@NotNull
@Override
public Mono<Void> filter(@NotNull ServerWebExchange exchange, @NotNull WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求,不处理
int index = ArrayUtil.indexOfIgnoreCase(VALIDATE_URL, request.getURI().getPath());
if (index == ArrayUtil.INDEX_NOT_FOUND) {
return chain.filter(exchange);
}
// set gateway:captcha.register:1962247851@qq.com:string "\xac\xed\x00\x05t\x00\x06565396"
// 会造成 HttpMessageNotReadableException,得生成一个新的RequestBody
// 解決方法 https://blog.csdn.net/cjbfzxz/article/details/106653242
// 生成新的Request
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
DataBufferUtils.retain(dataBuffer);
final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
// 修改后的新的request
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@NotNull
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return Mono.from(
cachedFlux.flatMap(cachedDataBuffer -> {
JSONObject originalBody = JSONObject.parseObject(IoUtil.read(cachedDataBuffer.asInputStream(), StandardCharsets.UTF_8));
String code = originalBody.getString(CODE);
try {
switch (index) {
case 0:
captchaService.checkLoginCaptcha(originalBody.getString(OR_NUMBER), code);
break;
case 1:
captchaService.checkRegisterCaptcha(originalBody.getString(EMAIL), code);
break;
default:
}
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), Result.fail(e.getMessage()));
}
})
);
});
}
}
```
# 关键代码
```java
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
```
# 过滤器代码ValidateCodeFilter.java
```java
@RequiredArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
@Component
public class ValidateCodeFilter implements WebFilter {
private final static String[] VALIDATE_URL = new String[]{"/login", "/upms/user/register"};
private static final String OR_NUMBER = "orNumber";
private static final String EMAIL = "email";
private static final String CODE = "code";
private final ICaptchaService captchaService;
@NotNull
@Override
public Mono<Void> filter(@NotNull ServerWebExchange exchange, @NotNull WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求,不处理
int index = ArrayUtil.indexOfIgnoreCase(VALIDATE_URL, request.getURI().getPath());
if (index == ArrayUtil.INDEX_NOT_FOUND) {
return chain.filter(exchange);
}
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
// TODO 会造成 HttpMessageNotReadableException,得生成一个新的RequestBody
return serverRequest.bodyToMono(JSONObject.class)
.flatMap(obj -> {
String code = obj.getString(CODE);
try {
switch (index) {
case 0:
captchaService.checkLoginCaptcha(obj.getString(OR_NUMBER), code);
break;
case 1:
captchaService.checkLoginCaptcha(obj.getString(EMAIL), code);
break;
default:
}
return chain.filter(exchange);
} catch (Exception e) {
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), Result.fail(e.getMessage()));
}
});
}
}
```
# 参考
[Spring Cloud Gateway 之获取请求体(Request Body)的几种方式 - 码农的进击 - 博客园 (cnblogs.com)](https://www.cnblogs.com/hyf-huangyongfei/p/12849406.html)
`ReadBodyRoutePredicateFactory`
附上 `spring-cloud-gateway-server 3.0.3 ` 版本中的 `ModifyRequestBodyGatewayFilterFactory` 源码
org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
```java
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.filter.factory.rewrite;
import java.util.List;
import java.util.function.Function;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
/**
* GatewayFilter that modifies the request body.
*/
public class ModifyRequestBodyGatewayFilterFactory
extends AbstractGatewayFilterFactory<ModifyRequestBodyGatewayFilterFactory.Config> {
private final List<HttpMessageReader<?>> messageReaders;
public ModifyRequestBodyGatewayFilterFactory() {
super(Config.class);
this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
}
public ModifyRequestBodyGatewayFilterFactory(List<HttpMessageReader<?>> messageReaders) {
super(Config.class);
this.messageReaders = messageReaders;
}
@Override
@SuppressWarnings("unchecked")
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Class inClass = config.getInClass();
ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
// TODO: flux or mono
Mono<?> modifiedBody = serverRequest.bodyToMono(inClass)
.flatMap(originalBody -> config.getRewriteFunction().apply(exchange, originalBody))
.switchIfEmpty(Mono.defer(() -> (Mono) config.getRewriteFunction().apply(exchange, null)));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, config.getOutClass());
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
// the new content type will be computed by bodyInserter
// and then set in the request decorator
headers.remove(HttpHeaders.CONTENT_LENGTH);
// if the body is changing content types, set it here, to the bodyInserter
// will know about it
if (config.getContentType() != null) {
headers.set(HttpHeaders.CONTENT_TYPE, config.getContentType());
}
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
// .log("modify_request", Level.INFO)
.then(Mono.defer(() -> {
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
return chain.filter(exchange.mutate().request(decorator).build());
})).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> release(exchange,
outputMessage, throwable));
}
@Override
public String toString() {
return filterToStringCreator(ModifyRequestBodyGatewayFilterFactory.this)
.append("Content type", config.getContentType()).append("In class", config.getInClass())
.append("Out class", config.getOutClass()).toString();
}
};
}
protected Mono<Void> release(ServerWebExchange exchange, CachedBodyOutputMessage outputMessage,
Throwable throwable) {
if (outputMessage.isCached()) {
return outputMessage.getBody().map(DataBufferUtils::release).then(Mono.error(throwable));
}
return Mono.error(throwable);
}
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(headers);
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
}
else {
// TODO: this causes a 'HTTP/1.1 411 Length Required' // on
// httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
public static class Config {
private Class inClass;
private Class outClass;
private String contentType;
private RewriteFunction rewriteFunction;
public Class getInClass() {
return inClass;
}
public Config setInClass(Class inClass) {
this.inClass = inClass;
return this;
}
public Class getOutClass() {
return outClass;
}
public Config setOutClass(Class outClass) {
this.outClass = outClass;
return this;
}
public RewriteFunction getRewriteFunction() {
return rewriteFunction;
}
public Config setRewriteFunction(RewriteFunction rewriteFunction) {
this.rewriteFunction = rewriteFunction;
return this;
}
public <T, R> Config setRewriteFunction(Class<T> inClass, Class<R> outClass,
RewriteFunction<T, R> rewriteFunction) {
setInClass(inClass);
setOutClass(outClass);
setRewriteFunction(rewriteFunction);
return this;
}
public String getContentType() {
return contentType;
}
public Config setContentType(String contentType) {
this.contentType = contentType;
return this;
}
}
}
```
评论
楼主暂时不想被别人评论哦~
已自动恢复阅读位置、日/夜间模式参数