base-api-gateway - 代码7

package com.hihonor.honorhome.component.apigateway.handler;

import com.hihonor.honorhome.common.response.BmsBaseResponse;

import javax.servlet.http.HttpServletRequest;

/**
 * 请求重复校验处理
 */
public interface RepeatRequestHandler {
    /**
     * 请求重复校
     * @param request HttpServletRequest
     * @return BmsBaseResponse
     */
    BmsBaseResponse checkRandomRequestId(HttpServletRequest request);
}
package com.hihonor.honorhome.component.apigateway.handler.impl;

import com.hihonor.honorhome.common.component.RedisKeyBuilder;
import com.hihonor.honorhome.common.response.BmsBaseResponse;
import com.hihonor.honorhome.component.apigateway.common.Constants;
import com.hihonor.honorhome.component.apigateway.common.HeaderConstant;
import com.hihonor.honorhome.component.apigateway.common.ResponseEnum;
import com.hihonor.honorhome.component.apigateway.handler.RepeatRequestHandler;

import com.alibaba.nacos.api.config.annotation.NacosValue;

import io.lettuce.core.RedisConnectionException;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;

/**
 * 请求重复校验处理
 */
@Slf4j
@Component
public class DefaultRepeatRequestHandler implements RepeatRequestHandler {
    @NacosValue(value = "${request.expiration.time}", autoRefreshed = true)
    private int expirationTime;

    @NacosValue(value = "${request.min.retry}", autoRefreshed = true)
    private Integer requestMinRetry;

    @NacosValue(value = "${request.max.retry}", autoRefreshed = true)
    private Integer requestMaxRetry;

    @NacosValue(value = "${honorhome-apigateway.compatible.version}", autoRefreshed = true)
    private String[] versions;

    @Autowired
    private RedisKeyBuilder gatewayRedisKeyBuilder;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public BmsBaseResponse checkRandomRequestId(HttpServletRequest request) {
        String appVersionName = request.getHeader(HeaderConstant.HEADER_APP_VERSION_NAME);
        if (StringUtils.isBlank(appVersionName)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-app-version-name is empty!");
        }
        String requestNonce = getRequestNonce(request, appVersionName);
        if (StringUtils.isBlank(requestNonce)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-request-nonce is empty!");
        }
        String timestamp = request.getHeader(HeaderConstant.HEADER_TIMESTAMP);
        if (StringUtils.isBlank(timestamp)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-timestamp is empty!");
        }
        String redisKey = String.format(Locale.ROOT, "API-GATEWAY:%s", requestNonce + ":" + timestamp);
        String key = gatewayRedisKeyBuilder.buildKey(Constants.BUSINESS_NAME, Constants.REQUEST_NAME, redisKey);
        try {
            String redisValue = stringRedisTemplate.opsForValue().get(key);
            if (redisValue == null) {
                String requestValue = String.valueOf(requestMinRetry);
                // 如果不存在,说明第一次请求,直接插入
                stringRedisTemplate.opsForValue().set(key, requestValue, expirationTime, TimeUnit.SECONDS);
            } else {
                Integer valueFrequency = Integer.parseInt(redisValue) + 1;
                log.warn("{}: retry times {}", requestNonce, valueFrequency);
                log.warn("url: {}", request.getRequestURL());
                if (valueFrequency > requestMaxRetry) {
                    log.error("{}: retry more than three times in 15 minute", key);
                    return BmsBaseResponse.error(ResponseEnum.REQUEST_RETRY_FAILED);
                }
                stringRedisTemplate.opsForValue().set(key, String.valueOf(valueFrequency), 0);
            }
            return BmsBaseResponse.OK;
        } catch (RedisConnectionException e) {
            log.error("checkRandomRequestId redis connect error", e);
            return new BmsBaseResponse(ResponseEnum.OK.getCode(), ResponseEnum.OK.getEnMessage());
        } catch (Exception e) {
            log.error("checkRandomRequestId handle error", e);
            return BmsBaseResponse.error(ResponseEnum.ERROR);
        }
    }

    private String getRequestNonce(HttpServletRequest request, String appVersionName) {
        String requestNonce;
        List<String> versionList = Arrays.asList(versions);
        if (!CollectionUtils.isEmpty(versionList) && versionList.contains(appVersionName)) {
            requestNonce = StringUtils.isBlank(request.getHeader(HeaderConstant.HEADER_REQUEST_NONCE))
                ? request.getHeader(HeaderConstant.HEADER_REQUEST_ID)
                : request.getHeader(HeaderConstant.HEADER_REQUEST_NONCE);
        } else {
            requestNonce = request.getHeader(HeaderConstant.HEADER_REQUEST_NONCE);
        }
        return requestNonce;
    }
}
package com.hihonor.honorhome.component.apigateway.handler;

import com.hihonor.honorhome.common.response.BmsBaseResponse;

import javax.servlet.http.HttpServletRequest;

/**
 * 功能描述:
 * 校验REQUEST HEADER
 */
public interface RequestHeaderHandler {
    /**
     * 检查REQUEST HEADER中的参数
     *
     * @param request request
     * @return 校验是否通过的标识
     */
    BmsBaseResponse checkHeader(HttpServletRequest request);
}
package com.hihonor.honorhome.component.apigateway.handler.impl;

import com.hihonor.honorhome.common.constants.NumberConstant;
import com.hihonor.honorhome.common.response.BmsBaseResponse;
import com.hihonor.honorhome.component.apigateway.common.HeaderConstant;
import com.hihonor.honorhome.component.apigateway.common.ResponseEnum;
import com.hihonor.honorhome.component.apigateway.handler.RequestHeaderHandler;

import com.alibaba.nacos.api.config.annotation.NacosValue;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

/**
 * 功能描述:
 *  校验REQUEST HEADER
 *
 * @author zw0067467
 * @since 2022-06-10
 */
public class DefaultRequestHeaderHandler implements RequestHeaderHandler {
    @NacosValue(value = "${honorhome-apigateway.compatible.version}", autoRefreshed = true)
    private String[] versions;

    private final Set<String> countrySet = new HashSet<>();

    /**
     * 初始化countrySet
     */
    @PostConstruct
    public void init() {
        // 国家
        String[] countryCodes = Locale.getISOCountries();
        if (countryCodes.length > 0) {
            for (String countryCode : countryCodes) {
                if (StringUtils.isBlank(countryCode)) {
                    continue;
                }
                countrySet.add(countryCode);
            }
        }
    }

    /**
     * 检查REQUEST HEADER中的参数
     *
     * @param request request
     * @return 校验是否通过的标识
     */
    @Override
    public BmsBaseResponse checkHeader(HttpServletRequest request) {
        String appVersionName = request.getHeader(HeaderConstant.HEADER_APP_VERSION_NAME);
        if (StringUtils.isBlank(appVersionName)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-app-version-name is empty!");
        }
        if (appVersionName.length() > NumberConstant.NUMBER_64) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-app-version-name is too long!");
        }
        String requestNonce = getRequestNonce(request, appVersionName);
        if (StringUtils.isBlank(requestNonce)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-request-nonce is empty!");
        }
        String country = request.getHeader(HeaderConstant.HEADER_COUNTRY);
        if (StringUtils.isBlank(country)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-country is empty!");
        }
        if (!isValidCountry(country)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-country is invalid!");
        }
        return checkHeaderParam(request);
    }

    /**
     * 校验国家简称是否符合ISO3166标准
     *
     * @param country 国家简称
     * @return 是否符合ISO标准
     */
    private boolean isValidCountry(String country) {
        return countrySet.contains(country);
    }

    /**
     * 请求头参数校验
     *
     * @param request Http请求
     * @return 是否符合校验规则
     */
    private BmsBaseResponse checkHeaderParam(HttpServletRequest request) {
        String userId = request.getHeader(HeaderConstant.HEADER_USER_ID);
        if (StringUtils.isNotBlank(userId)) {
            try {
                Base64.Decoder decoder = Base64.getDecoder();
                String user = new String(decoder.decode(userId), StandardCharsets.UTF_8);
                if (user.length() > NumberConstant.NUMBER_64) {
                    return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-user-id is too long!");
                }
            } catch (IllegalArgumentException e) {
                return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-user-id is illegal!");
            }
        }

        String udid = request.getHeader(HeaderConstant.HEADER_UDID);
        if (StringUtils.isNotBlank(udid)) {
            try {
                Base64.Decoder decoder = Base64.getDecoder();
                decoder.decode(udid);
            } catch (IllegalArgumentException e) {
                return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-udid is illegal!");
            }
        }

        String appId = request.getHeader(HeaderConstant.HEADER_APP_ID);
        if (StringUtils.isBlank(appId)) {
            return new BmsBaseResponse(ResponseEnum.BAD_REQUEST.getCode(), "x-app-id is empty!");
        }
        return BmsBaseResponse.OK;
    }

    /**
     * 获取nonce参数,appVersionName符合条件,nonce为空的情况下会自动补全
     *
     * @param request request
     * @param appVersionName appVersionName
     * @return requestNonce
     */
    private String getRequestNonce(HttpServletRequest request, String appVersionName) {
        String requestNonce;
        List<String> versionList = Arrays.asList(versions);
        if (!CollectionUtils.isEmpty(versionList) && versionList.contains(appVersionName)) {
            requestNonce = StringUtils.isBlank(request.getHeader(HeaderConstant.HEADER_REQUEST_NONCE))
                ? request.getHeader(HeaderConstant.HEADER_REQUEST_ID)
                : request.getHeader(HeaderConstant.HEADER_REQUEST_NONCE);
        } else {
            requestNonce = request.getHeader(HeaderConstant.HEADER_REQUEST_NONCE);
        }
        return requestNonce;
    }
}
package com.hihonor.honorhome.component.apigateway.handler;

import javax.servlet.http.HttpServletRequest;

/**
 * 功能描述:
 * 设置TRACE_ID
 * 打印REQUEST 相关日志
 * @author zw0067467
 * @since 2022-06-10
 */
public interface RequestLogHandler {
    /**
     * 设置TRACE_ID
     * 打印REQUEST 相关日志
     *
     * @param request request
     */
    void logRequest(HttpServletRequest request);
}
package com.hihonor.honorhome.component.apigateway.handler.impl;

import com.hihonor.honorhome.component.apigateway.common.HeaderConstant;
import com.hihonor.honorhome.component.apigateway.handler.RequestLogHandler;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import javax.servlet.http.HttpServletRequest;

/**
 * 功能描述
 * 设置TRACE_ID
 * 打印REQUEST 相关日志
 */
@Slf4j
public class DefaultRequestLogHandler implements RequestLogHandler {
    /**
     * 将request_id作为trace_id
     */
    private static final String TRACE_ID_KEY = HeaderConstant.HEADER_REQUEST_ID;

    /**
     * 设置TRACE_ID
     * 处理REQUEST 相关的日志
     *
     * @param request request
     */
    @Override
    public void logRequest(HttpServletRequest request) {
        if (request == null) {
            log.error("request is null");
            return;
        }
        // 白名单请求不需要trace
        Object attribute = request.getAttribute(HeaderConstant.IS_WHITE_URL);
        // 不属于白名单才会进行tracing
        if (!(HeaderConstant.IS_WHITE_URL_TRUE_VALUE.equals(attribute))) {
            // 设置traceId
            String traceId = request.getHeader(TRACE_ID_KEY);
            if (StringUtils.isBlank(traceId)) {
                traceId = "NULL";
            }
            MDC.put(TRACE_ID_KEY, traceId);
        }

        log.info("receive request,uri:{}", request.getRequestURI());
    }
}
package com.hihonor.honorhome.component.apigateway.handler;

import com.hihonor.honorhome.common.response.BmsBaseResponse;

import javax.servlet.http.HttpServletRequest;

/**
 * 权限校验处理器
 */
public interface UserAuthHandler {
    /**
     * 权限校验
     * @param request HttpServletRequest
     * @return BaseResponse
     */
    BmsBaseResponse userAuth(HttpServletRequest request);
}
package com.hihonor.honorhome.component.apigateway.handler.impl;

import com.hihonor.honorhome.common.response.BmsBaseResponse;
import com.hihonor.honorhome.component.apigateway.common.Constants;
import com.hihonor.honorhome.component.apigateway.common.HeaderConstant;
import com.hihonor.honorhome.component.apigateway.common.ResponseEnum;
import com.hihonor.honorhome.component.apigateway.handler.UserAuthHandler;
import com.hihonor.honorhome.component.apigateway.utils.TokenUtils;

import com.alibaba.nacos.api.config.annotation.NacosValue;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

/**
 * 权限校验处理器
 */
@Slf4j
@Component
public class DefaultUserAuthHandler implements UserAuthHandler {
    @NacosValue(value = "${auth.appIds}", autoRefreshed = true)
    private String[] authAppIds;

    @NacosValue(value = "${honorhome-apigateway.ignore.url:null}", autoRefreshed = true)
    private String[] ignoreUrlLists;

    @Autowired
    private TokenUtils tokenUtils;

    @Override
    public BmsBaseResponse userAuth(HttpServletRequest request) {
        if (request == null) {
            return BmsBaseResponse.error(ResponseEnum.BAD_REQUEST);
        }
        try {
            String appId = request.getHeader(HeaderConstant.HEADER_APP_ID);
            // 先验证appId
            if (StringUtils.isNotBlank(appId) && !checkAppId(appId)) {
                log.error("[appId match error,appId is {}]", appId);
                return BmsBaseResponse.error(ResponseEnum.AUTH_VERIFY_INVALID);
            }
            String token = request.getHeader(HeaderConstant.HEADER_TOKEN);
            String userId = request.getHeader(HeaderConstant.HEADER_USER_ID);
            // appid缺失或者验证不通过验证token
            if (StringUtils.isNotBlank(token) && StringUtils.isNotBlank(userId)) {
                return tokenUtils.tokenAuth(token, userId, Constants.BUSINESS_NAME);
            } else {
                // 验证url,未带用户信息同时不需要用户登录的直接跳过
                List<String> stringList = Arrays.asList(ignoreUrlLists);
                if (!stringList.isEmpty()) {
                    String uri = request.getRequestURI();
                    for (String ignoreUrl : stringList) {
                        if (uri.startsWith(ignoreUrl)) {
                            return BmsBaseResponse.OK;
                        }
                    }
                }
                log.error("[url filter error,url is {}]", request.getRequestURI());
                return BmsBaseResponse.error(ResponseEnum.AUTH_VERIFY_INVALID);
            }
        } catch (Exception e) {
            log.error("auth error", e);
            return BmsBaseResponse.error(ResponseEnum.API_GATEWAY_AUTH_ERROR);
        }
    }

    private boolean checkAppId(String appId) {
        List<String> authLists = Arrays.asList(authAppIds);
        if (!CollectionUtils.isEmpty(authLists)) {
            return authLists.contains(appId);
        }
        return false;
    }
}
posted @ 2022-07-27 12:17  kehuaihan  阅读(42)  评论(0编辑  收藏  举报