From bf0b9dfc63afead1743cfef6072e04893537103b Mon Sep 17 00:00:00 2001 From: wxz Date: Tue, 12 Oct 2021 09:23:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=9A=201.CpAuthGatewayFi?= =?UTF-8?q?lter=E7=9A=84=E9=80=BB=E8=BE=91=E6=8A=BD=E5=8F=96=E4=B8=BA?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E7=9A=84=E7=B1=BB=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=92=88=E5=AF=B9=E6=AD=A4filter=E7=9A=84=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=8B=A6=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/CpAuthGatewayFilterFactory.java | 214 +------------- .../com/epmet/filter/EpmetGatewayFilter.java | 263 ++++++++++++++++++ .../healthcheck/HealthCheckController.java | 27 ++ 3 files changed, 295 insertions(+), 209 deletions(-) create mode 100644 epmet-gateway/src/main/java/com/epmet/filter/EpmetGatewayFilter.java create mode 100644 epmet-gateway/src/main/java/com/epmet/healthcheck/HealthCheckController.java diff --git a/epmet-gateway/src/main/java/com/epmet/filter/CpAuthGatewayFilterFactory.java b/epmet-gateway/src/main/java/com/epmet/filter/CpAuthGatewayFilterFactory.java index ee9125db57..4ed4a4cf66 100644 --- a/epmet-gateway/src/main/java/com/epmet/filter/CpAuthGatewayFilterFactory.java +++ b/epmet-gateway/src/main/java/com/epmet/filter/CpAuthGatewayFilterFactory.java @@ -1,38 +1,14 @@ package com.epmet.filter; -import com.alibaba.fastjson.JSON; import com.epmet.auth.ExternalAuthProcessor; import com.epmet.auth.InternalAuthProcessor; -import com.epmet.commons.tools.constant.AppClientConstant; -import com.epmet.commons.tools.exception.ExceptionUtils; -import com.epmet.commons.tools.exception.RenException; -import com.epmet.commons.tools.utils.IpUtils; -import com.epmet.commons.tools.utils.Result; -import com.epmet.constant.AuthTypeConstant; -import com.epmet.constant.TokenHeaderKeyConstant; -import com.epmet.openapi.constant.AuthTypes; -import com.epmet.openapi.constant.RequestParamKeys; -import com.epmet.utils.ServerHttpRequestUtils; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; -import org.springframework.util.MultiValueMap; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import java.nio.charset.StandardCharsets; import java.util.*; /** @@ -42,10 +18,9 @@ import java.util.*; * @since 1.0.0 */ @Component("CpAuth") +@Slf4j public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactory { - private Logger logger = LoggerFactory.getLogger(getClass()); - @Autowired private InternalAuthProcessor internalAuthProcessor; @@ -61,181 +36,6 @@ public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactory { - if (!config.isEnabled()) { - return chain.filter(exchange); - } - - ServerHttpRequest request = exchange.getRequest(); - - //0.添加流水号 - String tranSerial = getTranserialFromRequestHeader(request); - if (StringUtils.isBlank(tranSerial)) { - tranSerial = generateTransactionSerial(); - // 设置当前线程名 - request.mutate().header(AppClientConstant.TRANSACTION_SERIAL_KEY, new String[]{tranSerial}); - } - Thread.currentThread().setName(tranSerial); - - //1.打印请求信息 - logRequest(request); - - //2.获取请求路径,参数 - String requestUri = request.getPath().pathWithinApplication().value(); - MultiValueMap queryParams = request.getQueryParams(); - String queryParamsStr = convertQueryParams2String(queryParams); - logger.info("当前requestUri=[" + requestUri.concat(queryParamsStr) + "],客户端Id:{}", IpUtils.getClientIp(request)); - - //3.认证 - String authType = getAuthType(request); - logger.info("认证类型为:{}", authType); - try { - switch (authType) { - case AuthTypeConstant.AUTH_TYPE_ALL: - externalAuthProcessor.auth(exchange, chain); - internalAuthProcessor.auth(exchange, chain); - break; - case AuthTypeConstant.AUTH_TYPE_EXTERNAL: - externalAuthProcessor.auth(exchange, chain); - break; - case AuthTypeConstant.AUTH_TYPE_INTERNAL: - internalAuthProcessor.auth(exchange, chain); - break; - } - } catch (RenException e) { - logger.error("CpAuthGatewayFilterFactory认证出错RenException,错误信息:{}", ExceptionUtils.getErrorStackTrace(e)); - return response(exchange, new Result<>().error(e.getCode(), e.getMessage())); - } catch (Exception e) { - logger.error("CpAuthGatewayFilterFactory认证出错Exception,错误信息:{}", ExceptionUtils.getErrorStackTrace(e)); - return response(exchange, new Result<>().error(e.getMessage())); - } - - return chain.filter(exchange); - }; - } - - /** - * @Description 打印请求头 - * @param request 请求 - * @return void - * @author wxz - * @date 2021.08.18 11:18:00 - */ - private void logRequest(ServerHttpRequest request) { - HttpHeaders headers = request.getHeaders(); - StringBuilder sb = new StringBuilder("请求头:"); - for (Map.Entry> entry : headers.entrySet()) { - String headerKey = entry.getKey(); - List headerValue = entry.getValue(); - sb.append(headerKey).append(":").append(headerValue).append(","); - } - logger.info(sb.toString()); - } - - /** - * @Description 从request请求头中获取传递过来的流水号 - * @param request - * @return java.lang.String - * @author wxz - * @date 2021.08.18 15:55:30 - */ - private String getTranserialFromRequestHeader(ServerHttpRequest request) { - List tranSerials = request.getHeaders().get(AppClientConstant.TRANSACTION_SERIAL_KEY); - return CollectionUtils.isEmpty(tranSerials) ? null : tranSerials.get(0); - } - - /** - * @return - * @Description 将url参数转化为String - * @author wxz - * @date 2021.08.11 23:12 - */ - private String convertQueryParams2String(MultiValueMap queryParams) { - try { - if (queryParams == null || queryParams.size() == 0) { - return ""; - } - StringBuilder sb = new StringBuilder(""); - queryParams.entrySet().forEach(entry -> { - String key = entry.getKey(); - List values = entry.getValue(); - - String value = ""; - if (values != null && values.size() > 0) { - value = values.get(0); - } - - sb.append("&").append(key).append("=").append(value); - }); - String result = sb.toString(); - if (result.startsWith("&")) { - result = result.substring(1); - return "?".concat(result); - } - return ""; - } catch (Exception e) { - logger.warn("gateway中将url参数转化为String失败,程序继续执行,错误信息:".concat(ExceptionUtils.getErrorStackTrace(e))); - return ""; - } - } - - /** - * 判断需要执行的认证方式(内部应用认证还是外部应用认证) - * - * @return - */ - private String getAuthType(ServerHttpRequest request) { - //String requestUri = request.getPath().pathWithinApplication().value(); - - // 是否在外部认证列表中(外部认证列表中的url,是对外部应用开放的,只有在这个列表中的url才对外部应用开放) - //boolean inExtAuthPaths = false; - // - //for (String url : cpProperty.getExternalAuthUrls()) { - // if (antPathMatcher.match(url, requestUri)) { - // inExtAuthPaths = true; - // } - //} - - String authType = ServerHttpRequestUtils.getRequestParam(request, RequestParamKeys.AUTH_TYPE); - if (StringUtils.isNotBlank(authType) && AuthTypes.TAKE_TOKEN.equals(authType)) { - return AuthTypeConstant.AUTH_TYPE_EXTERNAL; - } - - boolean needExternal = StringUtils.isNotBlank(request.getHeaders().getFirst(TokenHeaderKeyConstant.ACCESS_TOKEN_HEADER_KEY)); - boolean needInternal = StringUtils.isNotBlank(request.getHeaders().getFirst(TokenHeaderKeyConstant.AUTHORIZATION_TOKEN_HEADER_KEY)); - - if (needExternal && needInternal) { - return AuthTypeConstant.AUTH_TYPE_ALL; - } - - if (needExternal) { - // url对外部应用开放,并且头里面有AccessToken,那么走外部应用认证 - return AuthTypeConstant.AUTH_TYPE_EXTERNAL; - } - - return AuthTypeConstant.AUTH_TYPE_INTERNAL; - } - - /** - * 获取事务流水号 - * - * @return - */ - public static String generateTransactionSerial() { - String[] letterPool = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n" - , "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 2; i++) { - sb.append(letterPool[(int) (Math.random() * 25)]); - } - - sb.append(System.currentTimeMillis()); - return sb.toString(); - } - public static class CpAuthConfig { /** @@ -255,12 +55,8 @@ public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactory response(ServerWebExchange exchange, Object object) { - String json = JSON.toJSONString(object); - DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8)); - exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); - exchange.getResponse().setStatusCode(HttpStatus.OK); - return exchange.getResponse().writeWith(Flux.just(buffer)); + @Override + public GatewayFilter apply(CpAuthConfig config) { + return new EpmetGatewayFilter(config, internalAuthProcessor, externalAuthProcessor); } - } diff --git a/epmet-gateway/src/main/java/com/epmet/filter/EpmetGatewayFilter.java b/epmet-gateway/src/main/java/com/epmet/filter/EpmetGatewayFilter.java new file mode 100644 index 0000000000..ef18f3d4de --- /dev/null +++ b/epmet-gateway/src/main/java/com/epmet/filter/EpmetGatewayFilter.java @@ -0,0 +1,263 @@ +package com.epmet.filter; + +import com.alibaba.fastjson.JSON; +import com.epmet.auth.ExternalAuthProcessor; +import com.epmet.auth.InternalAuthProcessor; +import com.epmet.commons.tools.constant.AppClientConstant; +import com.epmet.commons.tools.exception.EpmetErrorCode; +import com.epmet.commons.tools.exception.ExceptionUtils; +import com.epmet.commons.tools.exception.RenException; +import com.epmet.commons.tools.utils.IpUtils; +import com.epmet.commons.tools.utils.Result; +import com.epmet.constant.AuthTypeConstant; +import com.epmet.constant.TokenHeaderKeyConstant; +import com.epmet.openapi.constant.AuthTypes; +import com.epmet.openapi.constant.RequestParamKeys; +import com.epmet.utils.ServerHttpRequestUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * @Description Gateway过滤器,可用于认证,请求相关日志打印 + * @author wxz + * @date 2021.10.12 09:10:02 +*/ +@Slf4j +public class EpmetGatewayFilter implements GatewayFilter { + + private CpAuthGatewayFilterFactory.CpAuthConfig config; + + private InternalAuthProcessor internalAuthProcessor; + + private ExternalAuthProcessor externalAuthProcessor; + + public EpmetGatewayFilter(CpAuthGatewayFilterFactory.CpAuthConfig config, + InternalAuthProcessor internalAuthProcessor, + ExternalAuthProcessor externalAuthProcessor) { + this.config = config; + this.internalAuthProcessor = internalAuthProcessor; + this.externalAuthProcessor = externalAuthProcessor; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + try { + if (!config.isEnabled()) { + return chain.filter(exchange); + } + + return doFilter(exchange, chain); + } catch (RenException re) { + // 人为抛出,则携带错误码和错误信息响应给前端 + log.error("EpmetGatewayFilter认证出错RenException,错误信息:{}", ExceptionUtils.getErrorStackTrace(re)); + return response(exchange, new Result<>().error(re.getCode(), re.getMessage())); + } catch (Exception t) { + // 其他非人为抛出异常,打印日志,返回"服务器开小差..."给前端 + log.error("EpmetGatewayFilter认证出错Exception,错误信息:{}", ExceptionUtils.getErrorStackTrace(t)); + return response(exchange, new Result<>().error(EpmetErrorCode.SERVER_ERROR.getCode())); + } + } + + /** + * @description 执行请求过滤和验证 + * + * @param exchange + * @param chain + * @return + * @author wxz + * @date 2021.10.12 09:16:15 + */ + private Mono doFilter(ServerWebExchange exchange, GatewayFilterChain chain) { + + ServerHttpRequest request = exchange.getRequest(); + + //0.添加流水号 + String tranSerial = getTranserialFromRequestHeader(request); + if (StringUtils.isBlank(tranSerial)) { + tranSerial = generateTransactionSerial(); + // 设置当前线程名 + request.mutate().header(AppClientConstant.TRANSACTION_SERIAL_KEY, new String[]{tranSerial}); + } + Thread.currentThread().setName(tranSerial); + + //1.打印请求信息 + logRequest(request); + + //2.获取请求路径,参数 + String requestUri = request.getPath().pathWithinApplication().value(); + MultiValueMap queryParams = request.getQueryParams(); + String queryParamsStr = convertQueryParams2String(queryParams); + log.info("当前requestUri=[" + requestUri.concat(queryParamsStr) + "],客户端Id:{}", IpUtils.getClientIp(request)); + + //3.认证 + String authType = getAuthType(request); + log.info("认证类型为:{}", authType); + + switch (authType) { + case AuthTypeConstant.AUTH_TYPE_ALL: + externalAuthProcessor.auth(exchange, chain); + internalAuthProcessor.auth(exchange, chain); + break; + case AuthTypeConstant.AUTH_TYPE_EXTERNAL: + externalAuthProcessor.auth(exchange, chain); + break; + case AuthTypeConstant.AUTH_TYPE_INTERNAL: + internalAuthProcessor.auth(exchange, chain); + break; + } + + return chain.filter(exchange); + } + + /** + * @param exchange + * @param object + * @return + * @description 产生Response对象 + * @author wxz + * @date 2021.10.12 09:00:20 + */ + protected Mono response(ServerWebExchange exchange, Object object) { + String json = JSON.toJSONString(object); + DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8)); + exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8); + exchange.getResponse().setStatusCode(HttpStatus.OK); + return exchange.getResponse().writeWith(Flux.just(buffer)); + } + + /** + * @param request 请求 + * @return void + * @Description 打印请求头 + * @author wxz + * @date 2021.08.18 11:18:00 + */ + private void logRequest(ServerHttpRequest request) { + HttpHeaders headers = request.getHeaders(); + StringBuilder sb = new StringBuilder("请求头:"); + for (Map.Entry> entry : headers.entrySet()) { + String headerKey = entry.getKey(); + List headerValue = entry.getValue(); + sb.append(headerKey).append(":").append(headerValue).append(","); + } + log.info(sb.toString()); + } + + /** + * @param request + * @return java.lang.String + * @Description 从request请求头中获取传递过来的流水号 + * @author wxz + * @date 2021.08.18 15:55:30 + */ + private String getTranserialFromRequestHeader(ServerHttpRequest request) { + List tranSerials = request.getHeaders().get(AppClientConstant.TRANSACTION_SERIAL_KEY); + return CollectionUtils.isEmpty(tranSerials) ? null : tranSerials.get(0); + } + + /** + * @return + * @Description 将url参数转化为String + * @author wxz + * @date 2021.08.11 23:12 + */ + private String convertQueryParams2String(MultiValueMap queryParams) { + try { + if (queryParams == null || queryParams.size() == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(""); + queryParams.entrySet().forEach(entry -> { + String key = entry.getKey(); + List values = entry.getValue(); + + String value = ""; + if (values != null && values.size() > 0) { + value = values.get(0); + } + + sb.append("&").append(key).append("=").append(value); + }); + String result = sb.toString(); + if (result.startsWith("&")) { + result = result.substring(1); + return "?".concat(result); + } + return ""; + } catch (Exception e) { + log.warn("gateway中将url参数转化为String失败,程序继续执行,错误信息:".concat(ExceptionUtils.getErrorStackTrace(e))); + return ""; + } + } + + /** + * 判断需要执行的认证方式(内部应用认证还是外部应用认证) + * + * @return + */ + private String getAuthType(ServerHttpRequest request) { + //String requestUri = request.getPath().pathWithinApplication().value(); + + // 是否在外部认证列表中(外部认证列表中的url,是对外部应用开放的,只有在这个列表中的url才对外部应用开放) + //boolean inExtAuthPaths = false; + // + //for (String url : cpProperty.getExternalAuthUrls()) { + // if (antPathMatcher.match(url, requestUri)) { + // inExtAuthPaths = true; + // } + //} + + String authType = ServerHttpRequestUtils.getRequestParam(request, RequestParamKeys.AUTH_TYPE); + if (StringUtils.isNotBlank(authType) && AuthTypes.TAKE_TOKEN.equals(authType)) { + return AuthTypeConstant.AUTH_TYPE_EXTERNAL; + } + + boolean needExternal = StringUtils.isNotBlank(request.getHeaders().getFirst(TokenHeaderKeyConstant.ACCESS_TOKEN_HEADER_KEY)); + boolean needInternal = StringUtils.isNotBlank(request.getHeaders().getFirst(TokenHeaderKeyConstant.AUTHORIZATION_TOKEN_HEADER_KEY)); + + if (needExternal && needInternal) { + return AuthTypeConstant.AUTH_TYPE_ALL; + } + + if (needExternal) { + // url对外部应用开放,并且头里面有AccessToken,那么走外部应用认证 + return AuthTypeConstant.AUTH_TYPE_EXTERNAL; + } + + return AuthTypeConstant.AUTH_TYPE_INTERNAL; + } + + /** + * 获取事务流水号 + * + * @return + */ + public String generateTransactionSerial() { + String[] letterPool = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n" + , "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 2; i++) { + sb.append(letterPool[(int) (Math.random() * 25)]); + } + + sb.append(System.currentTimeMillis()); + return sb.toString(); + } +} \ No newline at end of file diff --git a/epmet-gateway/src/main/java/com/epmet/healthcheck/HealthCheckController.java b/epmet-gateway/src/main/java/com/epmet/healthcheck/HealthCheckController.java new file mode 100644 index 0000000000..881cc37548 --- /dev/null +++ b/epmet-gateway/src/main/java/com/epmet/healthcheck/HealthCheckController.java @@ -0,0 +1,27 @@ +package com.epmet.healthcheck; + +import com.epmet.commons.tools.utils.Result; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Description 健康检查。gateway中的/api前缀只用于匹配转发路由,自身的Controller的前缀为/ + * @author wxz + * @date 2021.10.11 17:56:34 +*/ +@RestController +@RequestMapping("/gateway/healthcheck") +public class HealthCheckController { + + /** + * http健康检查 + * @return + */ + @PostMapping("http") + public Result httpHealthCheck() { + return new Result(); + } + +}