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 fe757be23..6254d0a43 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 ceaaeed76..094fcc951 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.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 @@ @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 61c18ef6c..d6ce2e9cb 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.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 @@ @RequiredArgsConstructor public class PasswordDecoderFilter extends AbstractGatewayFilterFactory { - private static final List> messageReaders = HandlerStrategies.withDefaults().messageReaders(); - - private static final String PASSWORD = "password"; - - private static final String KEY_ALGORITHM = "AES"; - - private final GatewayConfigProperties gatewayConfig; - - 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); - } - - // 2. 不是密码登录模式直接跳过 - String grantType = request.getQueryParams().getFirst("grant_type"); - if (!StrUtil.equals(SecurityConstants.PASSWORD, grantType)) { - return chain.filter(exchange); - } - - // 3. 前端加密密文解密逻辑 - Class inClass = String.class; - Class outClass = String.class; - ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders); - - // 4. 解密生成新的报文 - Mono modifiedBody = serverRequest.bodyToMono(inClass).flatMap(decryptAES()); - - 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(); - } - }; - } + private final ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter; + + private static final String PASSWORD = "password"; + + private static final String KEY_ALGORITHM = "AES"; + + private final GatewayConfigProperties gatewayConfig; + + static { + // 关闭hutool 强制关闭Bouncy Castle库的依赖 + SecureUtil.disableBouncyCastle(); + } + + @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); + } + + return modifyRequestBodyFilter + .apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(String.class, String.class, + (webExchange, body) -> Mono.just(modifyRequestPassword(body)))) + .filter(exchange, chain); + }; + } + + /** + * 修改请求报文的密码密文为名为 + * @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())); + + // 获取请求密码并解密 + Map inParamsMap = HttpUtil.decodeParamMap(requestBody, CharsetUtil.CHARSET_UTF_8); + if (inParamsMap.containsKey(PASSWORD)) { + String password = aes.decryptStr(inParamsMap.get(PASSWORD)); + // 返回修改后报文字符 + inParamsMap.put(PASSWORD, password); + } + + 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 b01e9c057..20c1943b9 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.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. @@ -63,70 +65,56 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory { ServerHttpRequest request = exchange.getRequest(); - boolean isAuthToken = CharSequenceUtil.containsAnyIgnoreCase(request.getURI().getPath(), - SecurityConstants.OAUTH_TOKEN_URL); - - if (!isAuthToken || isRefreshTokenRequest(request)) { + // 不是登录请求,直接向下执行 + if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) { return chain.filter(exchange); } + // 客户端配置跳过,直接向下执行 boolean isIgnoreClient = configProperties.getIgnoreClients().contains(WebUtils.getClientId(request)); - try { - if (!isIgnoreClient) { - checkCode(request); - } - } - 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); - - monoSink.success(dataBuffer); - } - catch (JsonProcessingException jsonProcessingException) { - log.error("对象输出异常", jsonProcessingException); - monoSink.error(jsonProcessingException); - } - })); + if (isIgnoreClient) { + return chain.filter(exchange); } - 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); + } - /** - * 判断是否为刷新令牌请求 - * @param request HTTP请求 - * @return 是否为刷新令牌请求 - */ - private boolean isRefreshTokenRequest(ServerHttpRequest request) { - return StrUtil.equals(SecurityConstants.REFRESH_TOKEN, request.getQueryParams().getFirst("grant_type")); + // 根据 randomStr 参数判断验证码是否正常 + String code = requestBodyMap.get("code"); + String randomStr = requestBodyMap.getOrDefault("randomStr", + requestBodyMap.get(SecurityConstants.SMS_PARAMETER_NAME)); + checkCode(code, randomStr); + + return chain.filter(exchange.mutate().request(serverHttpRequest).build()); + }); + }; } /** - * 检查验证码 - * @param request HTTP请求 + * 检查验证码,错误扔出 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 = StrUtil.blankToDefault(request.getQueryParams().getFirst("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 fe7bb102e..08825fb69 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 @@ -22,7 +22,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; -import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.MediaType; @@ -62,7 +61,7 @@ public Mono handle(ServerWebExchange exchange, Throwable ex) { 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) {