From b58d5abfe6f6e45f06f26645dd404b8c75b1c857 Mon Sep 17 00:00:00 2001 From: lbw Date: Sun, 28 Jan 2024 17:10:28 +0800 Subject: [PATCH] =?UTF-8?q?:recycle:=20Refactoring=20code.=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=BD=91=E5=85=B3Form=20Body=20read=20=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E6=8F=90=E5=8D=87=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/constant/SecurityConstants.java | 10 -- .../gateway/config/GatewayConfiguration.java | 34 +++- .../gateway/filter/PasswordDecoderFilter.java | 150 +++++------------- .../filter/ValidateCodeGatewayFilter.java | 93 ++++++----- .../handler/GlobalExceptionHandler.java | 2 +- 5 files changed, 120 insertions(+), 169 deletions(-) diff --git a/pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java b/pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java index fe757be2..6254d0a4 100755 --- a/pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java +++ b/pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java @@ -47,11 +47,6 @@ public interface SecurityConstants { */ String FROM = "from"; - /** - * 请求header - */ - String HEADER_FROM_IN = FROM + "=" + FROM_IN; - /** * 默认登录URL */ @@ -82,11 +77,6 @@ public interface SecurityConstants { */ String NOOP = "{noop}"; - /*** - * 资源服务器默认bean名称 - */ - String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter"; - /** * 用户名 */ diff --git a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/GatewayConfiguration.java b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/GatewayConfiguration.java index ceaaeed7..094fcc95 100644 --- a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/GatewayConfiguration.java +++ b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/GatewayConfiguration.java @@ -7,6 +7,7 @@ import com.pig4cloud.pig.gateway.filter.ValidateCodeGatewayFilter; import com.pig4cloud.pig.gateway.handler.GlobalExceptionHandler; import com.pig4cloud.pig.gateway.handler.ImageCodeHandler; 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.Configuration; import org.springframework.data.redis.core.RedisTemplate; @@ -20,27 +21,56 @@ import org.springframework.data.redis.core.RedisTemplate; @EnableConfigurationProperties(GatewayConfigProperties.class) public class GatewayConfiguration { + /** + * 创建密码解码器过滤器 + * @param modifyRequestBodyGatewayFilterFactory 修改请求体网关过滤器工厂 + * @param configProperties 配置属性 + * @return 密码解码器过滤器 + */ @Bean - public PasswordDecoderFilter passwordDecoderFilter(GatewayConfigProperties configProperties) { - return new PasswordDecoderFilter(configProperties); + public PasswordDecoderFilter passwordDecoderFilter( + ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory, + GatewayConfigProperties configProperties) { + return new PasswordDecoderFilter(modifyRequestBodyGatewayFilterFactory, configProperties); } + /** + * 创建PigRequest全局过滤器 + * @return PigRequest全局过滤器 + */ @Bean public PigRequestGlobalFilter pigRequestGlobalFilter() { return new PigRequestGlobalFilter(); } + /** + * 创建验证码网关过滤器 + * @param configProperties 配置属性 + * @param objectMapper 对象映射器 + * @param redisTemplate Redis模板 + * @return 验证码网关过滤器 + */ @Bean public ValidateCodeGatewayFilter validateCodeGatewayFilter(GatewayConfigProperties configProperties, ObjectMapper objectMapper, RedisTemplate redisTemplate) { return new ValidateCodeGatewayFilter(configProperties, objectMapper, redisTemplate); } + /** + * 创建全局异常处理程序 + * @param objectMapper 对象映射器 + * @return 全局异常处理程序 + */ @Bean public GlobalExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) { return new GlobalExceptionHandler(objectMapper); } + /** + * 创建图片验证码处理器 + * @param redisTemplate Redis模板 + * @return 图片验证码处理器 + */ @Bean public ImageCodeHandler imageCodeHandler(RedisTemplate redisTemplate) { return new ImageCodeHandler(redisTemplate); diff --git a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/PasswordDecoderFilter.java b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/PasswordDecoderFilter.java index 61c18ef6..d6ce2e9c 100755 --- a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/PasswordDecoderFilter.java +++ b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/PasswordDecoderFilter.java @@ -29,28 +29,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; -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.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory; 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 javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; -import java.util.List; import java.util.Map; -import java.util.function.Function; /** * @author lengleng @@ -60,107 +46,55 @@ import java.util.function.Function; @RequiredArgsConstructor public class PasswordDecoderFilter extends AbstractGatewayFilterFactory { - private static final List> 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 { - // 关闭hutool 强制关闭Bouncy Castle库的依赖 - SecureUtil.disableBouncyCastle(); - } + static { + // 关闭hutool 强制关闭Bouncy Castle库的依赖 + SecureUtil.disableBouncyCastle(); + } - @Override - public GatewayFilter apply(Object config) { - return (exchange, chain) -> { - ServerHttpRequest request = exchange.getRequest(); - // 1. 不是登录请求,直接向下执行 - if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) { - return chain.filter(exchange); - } + @Override + public GatewayFilter apply(Object config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + // 不是登录请求,直接向下执行 + if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) { + return chain.filter(exchange); + } - // 2. 不是密码登录模式直接跳过 - String grantType = request.getQueryParams().getFirst("grant_type"); - if (!StrUtil.equals(SecurityConstants.PASSWORD, grantType)) { - return chain.filter(exchange); - } + return modifyRequestBodyFilter + .apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(String.class, String.class, + (webExchange, body) -> Mono.just(modifyRequestPassword(body)))) + .filter(exchange, chain); + }; + } - // 3. 前端加密密文解密逻辑 - Class inClass = String.class; - Class outClass = String.class; - ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders); + /** + * 修改请求报文的密码密文为名为 + * @param requestBody 请求报文 + * @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 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); - 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 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 getBody() { - return outputMessage.getBody(); - } - }; - } + return HttpUtil.toParams(inParamsMap, Charset.defaultCharset(), true); + } } diff --git a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/ValidateCodeGatewayFilter.java b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/ValidateCodeGatewayFilter.java index d23e433a..20c1943b 100644 --- a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/ValidateCodeGatewayFilter.java +++ b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/ValidateCodeGatewayFilter.java @@ -17,14 +17,14 @@ package com.pig4cloud.pig.gateway.filter; import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; 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.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; 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.gateway.config.GatewayConfigProperties; import lombok.RequiredArgsConstructor; @@ -32,13 +32,15 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilter; 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.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.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. @@ -56,68 +58,63 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory redisTemplate; + /** + * 应用网关过滤器 + * @param config 配置对象 + * @return 网关过滤器 + */ @Override public GatewayFilter apply(Object config) { + return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); - boolean isAuthToken = CharSequenceUtil.containsAnyIgnoreCase(request.getURI().getPath(), - SecurityConstants.OAUTH_TOKEN_URL); - // 不是登录请求,直接向下执行 - if (!isAuthToken) { - return chain.filter(exchange); - } - - // 刷新token,手机号登录(也可以这里进行校验) 直接向下执行 - String grantType = request.getQueryParams().getFirst("grant_type"); - if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) { + if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) { return chain.filter(exchange); } + // 客户端配置跳过,直接向下执行 boolean isIgnoreClient = configProperties.getIgnoreClients().contains(WebUtils.getClientId(request)); - try { - // only oauth and the request not in ignore clients need check code. - if (!isIgnoreClient) { - checkCode(request); + if (isIgnoreClient) { + return chain.filter(exchange); + } + + // 构建缓存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 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(); - return response.writeWith(Mono.create(monoSink -> { - try { - byte[] bytes = objectMapper.writeValueAsBytes(R.failed(errMsg)); - DataBuffer dataBuffer = response.bufferFactory().wrap(bytes); + // 根据 randomStr 参数判断验证码是否正常 + String code = requestBodyMap.get("code"); + String randomStr = requestBodyMap.getOrDefault("randomStr", + requestBodyMap.get(SecurityConstants.SMS_PARAMETER_NAME)); + checkCode(code, randomStr); - monoSink.success(dataBuffer); - } - catch (JsonProcessingException jsonProcessingException) { - log.error("对象输出异常", jsonProcessingException); - monoSink.error(jsonProcessingException); - } - })); - } - - return chain.filter(exchange); + return chain.filter(exchange.mutate().request(serverHttpRequest).build()); + }); }; } + /** + * 检查验证码,错误扔出 ValidateCodeException GlobalExceptionHandler统一处理 + * @param code 验证码 + * @param randomStr 请求参数 + * @throws ValidateCodeException 验证码异常 + */ @SneakyThrows - private void checkCode(ServerHttpRequest request) { - String code = request.getQueryParams().getFirst("code"); - + private void checkCode(String code, String randomStr) { if (CharSequenceUtil.isBlank(code)) { 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; Object codeObj = redisTemplate.opsForValue().get(key); diff --git a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/handler/GlobalExceptionHandler.java b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/handler/GlobalExceptionHandler.java index ee9f98fd..a79a96f9 100644 --- a/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/handler/GlobalExceptionHandler.java +++ b/pig-gateway/src/main/java/com/pig4cloud/pig/gateway/handler/GlobalExceptionHandler.java @@ -61,7 +61,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler { return response.writeWith(Mono.fromSupplier(() -> { DataBufferFactory bufferFactory = response.bufferFactory(); 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()))); } catch (JsonProcessingException e) {