♻️ Refactoring code. 重构网关Form Body read 逻辑提升性能

This commit is contained in:
lbw 2024-01-28 17:10:28 +08:00
parent 44dffda501
commit b58d5abfe6
5 changed files with 120 additions and 169 deletions

View File

@ -47,11 +47,6 @@ public interface SecurityConstants {
*/ */
String FROM = "from"; String FROM = "from";
/**
* 请求header
*/
String HEADER_FROM_IN = FROM + "=" + FROM_IN;
/** /**
* 默认登录URL * 默认登录URL
*/ */
@ -82,11 +77,6 @@ public interface SecurityConstants {
*/ */
String NOOP = "{noop}"; String NOOP = "{noop}";
/***
* 资源服务器默认bean名称
*/
String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";
/** /**
* 用户名 * 用户名
*/ */

View File

@ -7,6 +7,7 @@ import com.pig4cloud.pig.gateway.filter.ValidateCodeGatewayFilter;
import com.pig4cloud.pig.gateway.handler.GlobalExceptionHandler; import com.pig4cloud.pig.gateway.handler.GlobalExceptionHandler;
import com.pig4cloud.pig.gateway.handler.ImageCodeHandler; import com.pig4cloud.pig.gateway.handler.ImageCodeHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
@ -20,27 +21,56 @@ import org.springframework.data.redis.core.RedisTemplate;
@EnableConfigurationProperties(GatewayConfigProperties.class) @EnableConfigurationProperties(GatewayConfigProperties.class)
public class GatewayConfiguration { public class GatewayConfiguration {
/**
* 创建密码解码器过滤器
* @param modifyRequestBodyGatewayFilterFactory 修改请求体网关过滤器工厂
* @param configProperties 配置属性
* @return 密码解码器过滤器
*/
@Bean @Bean
public PasswordDecoderFilter passwordDecoderFilter(GatewayConfigProperties configProperties) { public PasswordDecoderFilter passwordDecoderFilter(
return new PasswordDecoderFilter(configProperties); ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory,
GatewayConfigProperties configProperties) {
return new PasswordDecoderFilter(modifyRequestBodyGatewayFilterFactory, configProperties);
} }
/**
* 创建PigRequest全局过滤器
* @return PigRequest全局过滤器
*/
@Bean @Bean
public PigRequestGlobalFilter pigRequestGlobalFilter() { public PigRequestGlobalFilter pigRequestGlobalFilter() {
return new PigRequestGlobalFilter(); return new PigRequestGlobalFilter();
} }
/**
* 创建验证码网关过滤器
* @param configProperties 配置属性
* @param objectMapper 对象映射器
* @param redisTemplate Redis模板
* @return 验证码网关过滤器
*/
@Bean @Bean
public ValidateCodeGatewayFilter validateCodeGatewayFilter(GatewayConfigProperties configProperties, public ValidateCodeGatewayFilter validateCodeGatewayFilter(GatewayConfigProperties configProperties,
ObjectMapper objectMapper, RedisTemplate redisTemplate) { ObjectMapper objectMapper, RedisTemplate redisTemplate) {
return new ValidateCodeGatewayFilter(configProperties, objectMapper, redisTemplate); return new ValidateCodeGatewayFilter(configProperties, objectMapper, redisTemplate);
} }
/**
* 创建全局异常处理程序
* @param objectMapper 对象映射器
* @return 全局异常处理程序
*/
@Bean @Bean
public GlobalExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) { public GlobalExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) {
return new GlobalExceptionHandler(objectMapper); return new GlobalExceptionHandler(objectMapper);
} }
/**
* 创建图片验证码处理器
* @param redisTemplate Redis模板
* @return 图片验证码处理器
*/
@Bean @Bean
public ImageCodeHandler imageCodeHandler(RedisTemplate redisTemplate) { public ImageCodeHandler imageCodeHandler(RedisTemplate redisTemplate) {
return new ImageCodeHandler(redisTemplate); return new ImageCodeHandler(redisTemplate);

View File

@ -29,28 +29,14 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest; 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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
/** /**
* @author lengleng * @author lengleng
@ -60,107 +46,55 @@ import java.util.function.Function;
@RequiredArgsConstructor @RequiredArgsConstructor
public class PasswordDecoderFilter extends AbstractGatewayFilterFactory { public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); private final ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
private static final String PASSWORD = "password"; private static final String PASSWORD = "password";
private static final String KEY_ALGORITHM = "AES"; private static final String KEY_ALGORITHM = "AES";
private final GatewayConfigProperties gatewayConfig; private final GatewayConfigProperties gatewayConfig;
static { static {
// 关闭hutool 强制关闭Bouncy Castle库的依赖 // 关闭hutool 强制关闭Bouncy Castle库的依赖
SecureUtil.disableBouncyCastle(); SecureUtil.disableBouncyCastle();
} }
@Override @Override
public GatewayFilter apply(Object config) { public GatewayFilter apply(Object config) {
return (exchange, chain) -> { return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
// 1. 不是登录请求直接向下执行 // 不是登录请求直接向下执行
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) { if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
return chain.filter(exchange); return chain.filter(exchange);
} }
// 2. 不是密码登录模式直接跳过 return modifyRequestBodyFilter
String grantType = request.getQueryParams().getFirst("grant_type"); .apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(String.class, String.class,
if (!StrUtil.equals(SecurityConstants.PASSWORD, grantType)) { (webExchange, body) -> Mono.just(modifyRequestPassword(body))))
return chain.filter(exchange); .filter(exchange, chain);
} };
}
// 3. 前端加密密文解密逻辑 /**
Class inClass = String.class; * 修改请求报文的密码密文为名为
Class outClass = String.class; * @param requestBody 请求报文
ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders); * @return 修改后的报文
*/
private String modifyRequestPassword(String requestBody) {
// 构建前端对应解密AES 因子
AES aes = new AES(Mode.CFB, Padding.NoPadding,
new SecretKeySpec(gatewayConfig.getEncodeKey().getBytes(), KEY_ALGORITHM),
new IvParameterSpec(gatewayConfig.getEncodeKey().getBytes()));
// 4. 解密生成新的报文 // 获取请求密码并解密
Mono<?> modifiedBody = serverRequest.bodyToMono(inClass).flatMap(decryptAES()); Map<String, String> inParamsMap = HttpUtil.decodeParamMap(requestBody, CharsetUtil.CHARSET_UTF_8);
if (inParamsMap.containsKey(PASSWORD)) {
String password = aes.decryptStr(inParamsMap.get(PASSWORD));
// 返回修改后报文字符
inParamsMap.put(PASSWORD, password);
}
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass); return HttpUtil.toParams(inParamsMap, Charset.defaultCharset(), true);
HttpHeaders headers = new HttpHeaders(); }
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
return chain.filter(exchange.mutate().request(decorator).build());
}));
};
}
/**
* 原文解密
*
* @return
*/
private Function decryptAES() {
return s -> {
// 构建前端对应解密AES 因子
AES aes = new AES(Mode.CFB, Padding.NoPadding,
new SecretKeySpec(gatewayConfig.getEncodeKey().getBytes(), KEY_ALGORITHM),
new IvParameterSpec(gatewayConfig.getEncodeKey().getBytes()));
// 获取请求密码并解密
Map<String, String> inParamsMap = HttpUtil.decodeParamMap((String) s, CharsetUtil.CHARSET_UTF_8);
if (inParamsMap.containsKey(PASSWORD)) {
String password = aes.decryptStr(inParamsMap.get(PASSWORD));
// 返回修改后报文字符
inParamsMap.put(PASSWORD, password);
} else {
log.error("非法请求数据:{}", s);
}
return Mono.just(HttpUtil.toParams(inParamsMap, Charset.defaultCharset(), true));
};
}
/**
* 报文转换
*
* @return
*/
private 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 > 0) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
} }

View File

@ -17,14 +17,14 @@
package com.pig4cloud.pig.gateway.filter; package com.pig4cloud.pig.gateway.filter;
import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException; import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.CacheConstants;
import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.exception.ValidateCodeException; import com.pig4cloud.pig.common.core.exception.ValidateCodeException;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.core.util.WebUtils; import com.pig4cloud.pig.common.core.util.WebUtils;
import com.pig4cloud.pig.gateway.config.GatewayConfigProperties; import com.pig4cloud.pig.gateway.config.GatewayConfigProperties;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -32,13 +32,15 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono; import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
/** /**
* The type Validate code gateway filter. * The type Validate code gateway filter.
@ -56,68 +58,63 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory<Obje
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
/**
* 应用网关过滤器
* @param config 配置对象
* @return 网关过滤器
*/
@Override @Override
public GatewayFilter apply(Object config) { public GatewayFilter apply(Object config) {
return (exchange, chain) -> { return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
boolean isAuthToken = CharSequenceUtil.containsAnyIgnoreCase(request.getURI().getPath(),
SecurityConstants.OAUTH_TOKEN_URL);
// 不是登录请求直接向下执行 // 不是登录请求直接向下执行
if (!isAuthToken) { if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
return chain.filter(exchange);
}
// 刷新token手机号登录也可以这里进行校验 直接向下执行
String grantType = request.getQueryParams().getFirst("grant_type");
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
return chain.filter(exchange); return chain.filter(exchange);
} }
// 客户端配置跳过直接向下执行
boolean isIgnoreClient = configProperties.getIgnoreClients().contains(WebUtils.getClientId(request)); boolean isIgnoreClient = configProperties.getIgnoreClients().contains(WebUtils.getClientId(request));
try { if (isIgnoreClient) {
// only oauth and the request not in ignore clients need check code. return chain.filter(exchange);
if (!isIgnoreClient) { }
checkCode(request);
// 构建缓存body可重复读获取form data
return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
// get cacheRequestBody
DataBuffer cachedRequestBody = exchange.getAttribute("cachedRequestBody");
CharBuffer charBuffer = StandardCharsets.UTF_8
.decode(Objects.requireNonNull(cachedRequestBody).asByteBuffer());
Map<String, String> requestBodyMap = HttpUtil.decodeParamMap(charBuffer.toString(),
CharsetUtil.CHARSET_UTF_8);
// 刷新请求跳过直接向下执行
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, requestBodyMap.get("grant_type"))) {
return chain.filter(exchange);
} }
}
catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
final String errMsg = e.getMessage(); // 根据 randomStr 参数判断验证码是否正常
return response.writeWith(Mono.create(monoSink -> { String code = requestBodyMap.get("code");
try { String randomStr = requestBodyMap.getOrDefault("randomStr",
byte[] bytes = objectMapper.writeValueAsBytes(R.failed(errMsg)); requestBodyMap.get(SecurityConstants.SMS_PARAMETER_NAME));
DataBuffer dataBuffer = response.bufferFactory().wrap(bytes); checkCode(code, randomStr);
monoSink.success(dataBuffer); return chain.filter(exchange.mutate().request(serverHttpRequest).build());
} });
catch (JsonProcessingException jsonProcessingException) {
log.error("对象输出异常", jsonProcessingException);
monoSink.error(jsonProcessingException);
}
}));
}
return chain.filter(exchange);
}; };
} }
/**
* 检查验证码错误扔出 ValidateCodeException GlobalExceptionHandler统一处理
* @param code 验证码
* @param randomStr 请求参数
* @throws ValidateCodeException 验证码异常
*/
@SneakyThrows @SneakyThrows
private void checkCode(ServerHttpRequest request) { private void checkCode(String code, String randomStr) {
String code = request.getQueryParams().getFirst("code");
if (CharSequenceUtil.isBlank(code)) { if (CharSequenceUtil.isBlank(code)) {
throw new ValidateCodeException("验证码不能为空"); throw new ValidateCodeException("验证码不能为空");
} }
String randomStr = request.getQueryParams().getFirst("randomStr");
if (CharSequenceUtil.isBlank(randomStr)) {
randomStr = request.getQueryParams().getFirst(SecurityConstants.SMS_PARAMETER_NAME);
}
String key = CacheConstants.DEFAULT_CODE_KEY + randomStr; String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
Object codeObj = redisTemplate.opsForValue().get(key); Object codeObj = redisTemplate.opsForValue().get(key);

View File

@ -61,7 +61,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
return response.writeWith(Mono.fromSupplier(() -> { return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory(); DataBufferFactory bufferFactory = response.bufferFactory();
try { try {
log.warn("Error Spring Cloud Gateway : {} {}", exchange.getRequest().getPath(), ex.getMessage()); log.debug("Error Spring Cloud Gateway : {} {}", exchange.getRequest().getPath(), ex.getMessage());
return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage()))); return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));
} }
catch (JsonProcessingException e) { catch (JsonProcessingException e) {