3 changed files with 295 additions and 209 deletions
			
			
		@ -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<Void> 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<Void> 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<String, String> 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<Void> 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<String, List<String>> entry : headers.entrySet()) { | 
				
			|||
            String headerKey = entry.getKey(); | 
				
			|||
            List<String> 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<String> 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<String, String> queryParams) { | 
				
			|||
        try { | 
				
			|||
            if (queryParams == null || queryParams.size() == 0) { | 
				
			|||
                return ""; | 
				
			|||
            } | 
				
			|||
            StringBuilder sb = new StringBuilder(""); | 
				
			|||
            queryParams.entrySet().forEach(entry -> { | 
				
			|||
                String key = entry.getKey(); | 
				
			|||
                List<String> 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(); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -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(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
} | 
				
			|||
					Loading…
					
					
				
		Reference in new issue