OR博客
Spring Cloud Gateway在过滤器中获取请求body
OrdinaryRoad
创建于:2021-11-28 16:01:37
0
16
72
0
对于一些需要验证码的接口,可以设置过滤器,统一获取请求体中的验证码相关信息进行验证
# 最新解决方式 重点:使用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; } } } ```
评论
楼主暂时不想被别人评论哦~