自定义 Spring Gateway GlobalFilter 实现 黑名单、请求鉴权、请求加解密、请求签名验证

通过自定义 Spring Gateway GlobalFilter 实现 黑名单、请求鉴权、请求加解密、请求签名验证

自定义拦截器配置

定义 GatewayFilterProperties

@RefreshScope
@ConfigurationProperties(GatewayFilterProperties.PREFIX)
@Data
public class GatewayFilterProperties {

    public static final String PREFIX = "spring.cloud.gateway";

    /**
     * Specifies the name of the header on where to find the token (i.e.X-Authorization).
     */
    private String authorizationHeaderName = RequestAuthorizationFilter.AUTHORIZATION_HEADER;
    private String authorizationParamName = RequestAuthorizationFilter.AUTHORIZATION_PARAM;
    private String authorizationCookieName = RequestAuthorizationFilter.AUTHORIZATION_PARAM;

    /**
     * 类似Shiro的过滤链定义,用于初始化默认的过滤规则
     */
    private Map<String , String> filterMap = new LinkedHashMap<>();

    private RequestCrypto crypto = new RequestCrypto();

    private RequestSignature signature = new RequestSignature();

    @Data
    public static class RequestCrypto {

        /**
         * 是否启用传输机密性和传输完整性验过滤器
         */
        private boolean enabled;

        /**
         * 白名单,不需要验签,以逗号分割
         */
        private String whiteList;

        /**
         * 加密解密方式,default:默认的行为,internal:内部加密,flksec:弗兰科信息
         */
        private CryptoType type = CryptoType.NOOP;

    }

    @Data
    public static class RequestSignature {

        /**
         * 是否启用请求签名验证过滤器
         */
        private boolean enabled;

        /**
         * 白名单,不需要验签,以逗号分割
         */
        private String whiteList;

    }


}

定义 GatewayFilterConfiguration 用于初始化配置对象

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties({GatewayFilterProperties.class})
public class GatewayFilterConfiguration {

}

后续逻辑依赖的工具和对象

1、XHeaders 定义了系统额外增加的 Header 名称
public class XHeaders {

    /**
     * 客户端唯一请求ID
     */
    public static final String X_REQUEST_ID = "X-Request-Id";

    /**
     * JWT Token
     */
    public static final String X_AUTHORIZATION = "X-Authorization";

    /**
     * 经度
     */
    public static final String X_LONGITUDE = "X-Longitude";
    /**
     * 纬度
     */
    public static final String X_LATITUDE = "X-Latitude";
    /**
     * 国家地区编码:http://doc.chacuo.net/iso-3166-1
     */
    public static final String X_REGION = "X-Region";
    /**
     * 最新定位省份
     */
    public static final String X_PROVINCE = "X-Province";
    /**
     * 最新定位城市
     */
    public static final String X_CITY = "X-City";
    /**
     * 最新定位区
     */
    public static final String X_AREA = "X-Area";

    /**
     * 国际化(zh_CN:简体中文、zh_TW:繁体中文、en_US:英语)
     */
    public static final String X_LANGUAGE = "X-Language";

    /**
     * 客户端时区
     */
    public static final String X_TIMEZONE = "X-TimeZone";

    /**
     * 参数签名
     */
    public static final String X_SIGN = "X-Sign";

    /**
     * 请求加密方式,eg: "X-Encrypt": AES
     */
    public static final String X_ENCRYPT = "X-Encrypt";

    /**
     * 客户端应用ID
     */
    public static final String X_APP_ID = "X-App-Id";
    /**
     * 客户端应用渠道
     */
    public static final String X_APP_CHANNEL = "X-App-Channel";
    /**
     * 客户端应用版本号
     */
    public static final String X_APP_VERSION = "X-App-Version";
    /**
     * 客户端设备唯一标识
     */
    public static final String X_DEVICE_IMEI = "X-Device-IMEI";
    /**
     * IOS 6+的设备唯一标识
     */
    public static final String X_DEVICE_IDFA = "X-Device-IDFA";
    /**
     * IOS 设备识别码
     */
    public static final String X_DEVICE_OPENUDID = "X-Device-OPENUDID";
    /**
     * Android id原值
     */
    public static final String X_DEVICE_ANDROIDID = "X-Device-ANDROIDID";
    /**
     * Android Q及更高版本的设备号
     */
    public static final String X_DEVICE_OAID = "X-Device-OAID";
    /**
     * 客户端设备Mac地址
     */
    public static final String X_DEVICE_MAC = "X-Device-MAC";
    /**
     * 客户端设备型号
     */
    public static final String X_DEVICE_MODEL = "X-Device-Model";
    /**
     * 客户端网络类型( 2G、3G、4G、5G、Unknown)
     */
    public static final String X_DEVICE_NET_TYPE = "X-Device-NetType";
    /**
     * 客户端设备系统版本
     */
    public static final String X_DEVICE_OS = "X-OS";
    /**
     * 接口请求来源(用于行为统计)
     */
    public static final String X_REFERRER = "X-Referrer";
    /**
     * 调用此接口的菜单编码(用于PV统计)
     */
    public static final String X_FEATURE_CODE = "X-Feature-Code";
    /**
     * 调用此接口的功能操作编码(用于PV统计)
     */
    public static final String X_FEATURE_OPT_CODE = "X-Feature-Opt-Code";
    /**
     * 账户UID
     */
    public static final String X_UID = "X-Uid";
    /**
     * 用户名
     */
    public static final String X_UNAME = "X-Uname";
    /**
     * 用户Key:用户业务表中的唯一ID
     */
    public static final String X_UKEY = "X-Ukey";
    /**
     * 用户Code:用户业务表中的唯一编码
     */
    public static final String X_UCODE = "X-Ucode";
    /**
     * 角色ID(角色表Id)
     */
    public static final String X_RID = "X-Rid";
    /**
     * 角色Key:角色业务表中的唯一ID
     */
    public static final String X_RKEY = "X-Rkey";
    /**
     * 角色Code:角色业务表中的唯一编码
     */
    public static final String X_RCODE = "X-Rcode";


}
GatewayUtils 定义响应处理方法
@Slf4j
public class GatewayUtils {

    public static Mono<Void> returnMono(ServerHttpResponse response, ApiRestResponse<String> apiRestResponse) {
        byte[] bits = JSONObject.toJSONString(apiRestResponse).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); // 指定编码,否则在浏览器中会中文乱码
        return response.writeWith(Mono.just(buffer));
    }

    public static Mono<Void> returnMono(ServerWebExchange exchange, GatewayFilterChain chain){
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null){
                long executeTime = (System.currentTimeMillis() - startTime);
                log.info("耗时:{}ms" , executeTime);
                log.info("状态码:{}" , Objects.requireNonNull(exchange.getResponse().getStatusCode()).value());
            }
        }));
    }

}
SymmetricCryptoUtil 定义使用内部加解密模式时使用的加解密工具
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.StringUtils;

import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;

/**
 * 对称加密工具类
 */
public class SymmetricCryptoUtil {

    private static Cache<String, SymmetricCrypto> SYMMETRIC_CRYPTO_CACHE = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterAccess(60, TimeUnit.SECONDS)
            .build();
    private static Cache<String, HMac> HMAC_CACHE = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterAccess(60, TimeUnit.SECONDS)
            .build();

    /**
     * 获取 SymmetricCrypto
     * @param key 密钥
     * @param iv 偏移向量,加盐
     * @return SymmetricCrypto
     */
    public static SymmetricCrypto getSymmetricCrypto(String algorithmType, String mode, String padding, String key, String iv) {
        StringJoiner keyJoiner = new StringJoiner("_").add(algorithmType).add(mode).add(padding).add(key).add(iv);
        // 构造对称加密器
        return SYMMETRIC_CRYPTO_CACHE.get(keyJoiner.toString(), join -> {
            String[] keyArr =  join.split("_");
            String algorithmTypeStr = Objects.toString(keyArr[0], SM4.ALGORITHM_NAME);
            String modeStr = Objects.toString(keyArr[1], Mode.ECB.name());
            String paddingStr = Objects.toString(keyArr[2], Padding.PKCS5Padding.name());
            byte[] keyBytes = StringUtils.isBlank(keyArr[3]) ? null : keyArr[3].getBytes(CharsetUtil.CHARSET_UTF_8);
            byte[] ivBytes = StringUtils.isBlank(keyArr[4]) ? null : keyArr[4].getBytes(CharsetUtil.CHARSET_UTF_8);
            // 构造SM4加密器
            if(SM4.ALGORITHM_NAME.equalsIgnoreCase(algorithmType)){
                return new SM4(Mode.valueOf(algorithmTypeStr), Padding.valueOf(keyArr[2]), keyBytes, ivBytes);
            }
            // 构造AES加密器
            if(SymmetricAlgorithm.AES.name().equalsIgnoreCase(algorithmType)){
                return new AES(Mode.valueOf(modeStr), Padding.valueOf(paddingStr), keyBytes, ivBytes);
            }

            return new AES(Mode.valueOf(algorithmTypeStr), Padding.valueOf(keyArr[2]), keyBytes, ivBytes);
        });
    }

    /**
     * 获取 SymmetricCrypto
     * @param key 密钥
     * @param iv 偏移向量,加盐
     * @return SymmetricCrypto
     */
    public static SymmetricCrypto getSm4(String mode, String padding, String key, String iv) {
        return getSymmetricCrypto(SM4.ALGORITHM_NAME, mode, padding, key, iv);
    }

    /**
     * 获取aes
     * @param key 密钥,支持三种密钥长度:128、192、256位
     * @param iv 偏移向量,加盐
     * @return AES
     */
    public static SymmetricCrypto getAes(String mode, String padding, String key, String iv) {
        return getSymmetricCrypto(SymmetricAlgorithm.AES.name(), mode, padding, key, iv);
    }

    /**
     * 获取aes
     * @param hmacAlgorithm Hmac算法
     * @param key 密钥,支持三种密钥长度:128、192、256位
     * @return AES
     */
    public static HMac getHmac(HmacAlgorithm hmacAlgorithm, String key) {
        StringJoiner keyJoiner = new StringJoiner("_").add(hmacAlgorithm.getValue()).add(key);
        // 构造对称加密器
        return HMAC_CACHE.get(keyJoiner.toString(), join -> {
            String[] keyArr =  join.split("_");
            String hmacAlgorithmStr = Objects.toString(keyArr[0], HmacAlgorithm.HmacSM3.getValue());
            byte[] keyBytes = keyArr[1].getBytes(CharsetUtil.CHARSET_UTF_8);
            return new HMac(hmacAlgorithmStr, keyBytes);
        });
    }

}
定义响应对象 ApiRestResponse
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.ToString;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * model for interacting with client.
 */
@ApiModel(value = "ApiRestResponse", description = "接口响应对象")
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiRestResponse<T extends Object> {

    @ApiModelProperty(name = "code", dataType = "String", value = "成功或异常编码")
    private final int code;

    @ApiModelProperty(name = "status", dataType = "String", value = "旧接口成功、失败或异常辅助判断标记:success、fail、error", allowableValues = "success,fail,error")
    private final String status;

    @ApiModelProperty(name = "message", dataType = "String", value = "成功或异常消息")
    private final String message;

    @ApiModelProperty(name = "data", dataType = "java.lang.Object", value = "成功或异常数据")
    private T data;

    @ApiModelProperty(name = "error", dataType = "java.util.List<Map<String, String>>", value = "校验失败信息")
    private List<Map<String,String>> error;

    public ApiRestResponse() {
        this.code = ApiCode.SC_SUCCESS.getCode();
        this.status = Constants.RT_SUCCESS;
        this.message = ApiCode.SC_SUCCESS.getReason();
    }

    protected ApiRestResponse(final ApiCode code) {
        this.code = code.getCode();;
        this.status = code.getStatus();
        this.message = code.getReason();
    }

    protected ApiRestResponse(final ApiCode code, final T data) {
        this.code = code.getCode();;
        this.status = code.getStatus();
        this.message = code.getReason();
        this.data = data;
    }

    protected ApiRestResponse(final CustomApiCode code) {
        this.code = code.getCode();;
        this.status = code.getStatus();
        this.message = code.getReason();
    }

    protected ApiRestResponse(final CustomApiCode code, final T data) {
        this.code = code.getCode();;
        this.status = code.getStatus();
        this.message = code.getReason();
        this.data = data;
    }

    protected ApiRestResponse(final ApiCode code, final String message, final T data) {
        this.code = code.getCode();;
        this.status = code.getStatus();
        this.message = message;
        this.data = data;
    }

    protected ApiRestResponse(final ApiCode code, final String message, final T data, List<Map<String,String>> error) {
        this.code = code.getCode();;
        this.status = code.getStatus();
        this.message = message;
        this.data = data;
        this.error = error;
    }

    protected ApiRestResponse(final int code, final String message) {
        this(code, Constants.RT_SUCCESS, message);
    }

    protected ApiRestResponse(final int code, final String status, final String message) {
        this.code = code;
        this.status = status;
        this.message = message;
    }

    protected ApiRestResponse(final int code, final String message, final T data) {
        this(code, Constants.RT_SUCCESS, message, data);
    }

    protected ApiRestResponse(final int code, final String status, final String message, final T data) {
        this.code = code;
        this.status = status;
        this.message = message;
        this.data = data;
    }

    // success -----------------------------------------------------------------

    public static <T> ApiRestResponse<T> success(final String message) {
        return of(ApiCode.SC_SUCCESS, message, null);
    }

    public static <T> ApiRestResponse<T> success(final T data) {
        return of(ApiCode.SC_SUCCESS, data);
    }

    public static <T> ApiRestResponse<T> success(final ApiCode code,final T data) {
        return of(code, data);
    }

    public static <T> ApiRestResponse<T> success(final int code, final String message) {
        return of(code, Constants.RT_SUCCESS, message);
    }

    public static <T> ApiRestResponse<T> success(final ApiCode code, final String message) {
         return of(code, message, null);
    }

    // fail -----------------------------------------------------------------

    public static <T> ApiRestResponse<T> fail(final String message) {
        return of(ApiCode.SC_FAIL, message, null);
    }

    public static <T> ApiRestResponse<T> fail(final T data) {
        return of(ApiCode.SC_FAIL, data);
    }

    public static <T> ApiRestResponse<T> fail(final ApiCode code, final T data) {
        return of(code, data);
    }

    public static <T> ApiRestResponse<T> fail(final int code, final String message) {
        return of(code, Constants.RT_FAIL, message);
    }

    public static <T> ApiRestResponse<T> fail(final ApiCode code, final String message) {
         return of(code, message, null);
    }

    // error -----------------------------------------------------------------

    public static <T> ApiRestResponse<T> error(final String message) {
        return of(ApiCode.SC_INTERNAL_SERVER_ERROR, message, null);
    }

    public static <T> ApiRestResponse<T> error(final T data) {
        return of(ApiCode.SC_INTERNAL_SERVER_ERROR, data);
    }

    public static <T> ApiRestResponse<T> error(final ApiCode code, final T data) {
        return of(code, data);
    }

    public static <T> ApiRestResponse<T> error(final int code, final String message) {
        return of(code, Constants.RT_ERROR, message);
    }

    public static <T> ApiRestResponse<T> error(final ApiCode code, final String message) {
        return of(code, message, null);
    }

    public static <T> ApiRestResponse<T> error(final ApiCode code, final String message, List<Map<String,String>> error) {
        return of(code, message, null, error);
    }

    // -----------------------------------------------------------------

    public static <T> ApiRestResponse<T> of(final ApiCode code) {
        return new ApiRestResponse<T>(code);
    }

    public static <T> ApiRestResponse<T> of(final ApiCode code, final T data) {
        return new ApiRestResponse<T>(code, data);
    }

    public static <T> ApiRestResponse<T> of(final ApiCode code, final String message, final T data) {
        return new ApiRestResponse<T>(code, message, data);
    }

    public static <T> ApiRestResponse<T> of(final ApiCode code, final String message, final T data, List<Map<String,String>> error) {
        return new ApiRestResponse<T>(code, message, data, error);
    }

    public static <T> ApiRestResponse<T> of(final String code, final String message) {
        return new ApiRestResponse<T>(Integer.parseInt(code), message);
    }

    public static <T> ApiRestResponse<T> of(final int code, final String message) {
        return new ApiRestResponse<T>(code, message);
    }

    public static <T> ApiRestResponse<T> of(final String code, final String status, final String message) {
            return of(Integer.parseInt(code), status, message, null);
    }

    public static <T> ApiRestResponse<T> of(final int code, final String status, final String message) {
         return of(code, status, message, null);
    }

    public static <T> ApiRestResponse<T> of(final int code, final String status, final String message, final T data) {
        return new ApiRestResponse<T>(code, status, message, data);
    }

    public static <T> ApiRestResponse<T> of(final CustomApiCode code) {
        return new ApiRestResponse<T>(code);
    }

    public static <T> ApiRestResponse<T> of(final CustomApiCode code, final T data) {
        return new ApiRestResponse<T>(code, data);
    }


    public int getCode() {
        return code;
    }

    public String getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }

    public T getData() {
        return data;
    }

    @JsonIgnore
    public List<Map<String, String>> getError() {
        return error;
    }

    @JsonIgnore
    public boolean isSuccess() {
        return status == Constants.RT_SUCCESS || code == ApiCodeValue.SC_SUCCESS;
    }

    public Map<String, Object> toMap(){
        Map<String, Object> rtMap = new HashMap<String, Object>();
        rtMap.put("code", code);
        rtMap.put("status", status);
        rtMap.put("message", message);
        rtMap.put("data", data);
        return rtMap;
    }

}
WebFluxUtils
import lombok.extern.slf4j.Slf4j;
import org.springframework.biz.utils.StringUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import reactor.core.publisher.Flux;

import javax.servlet.http.HttpServletRequest;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class WebFluxUtils {

    private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
    private static final String X_REQUESTED_WITH = "X-Requested-With";
    private static final String CONTENT_TYPE_JSON = "application/json";
    private static final String[] xheaders = new String[]{"X-Forwarded-For", "x-forwarded-for"};
    private static final String[] headers = new String[]{"Cdn-Src-Ip", "Proxy-Client-IP", "WL-Proxy-Client-IP", "X-Real-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
    private static final String LOCAL_HOST = "localhost";
    private static final String LOCAL_IP6 = "0:0:0:0:0:0:0:1";
    private static final String LOCAL_IP = "127.0.0.1";
    private static final String UNKNOWN = "unknown";   

    public static boolean isAjaxResponse(ServerHttpRequest request ) {
        return isAjaxRequest(request) || isContentTypeJson(request) || isPostRequest(request);
    }

    public static boolean isObjectRequest(ServerHttpRequest request ) {
        return isPostRequest(request) && isContentTypeJson(request);
    }

    public static boolean isObjectRequest(HttpRequest request ) {
        return isPostRequest(request) && isContentTypeJson(request);
    }

    public static boolean isAjaxRequest(ServerHttpRequest request ) {
        return XML_HTTP_REQUEST.equals(request.getHeaders().getFirst(X_REQUESTED_WITH));
    }

    public static boolean isAjaxRequest(HttpRequest request ) {
        return request.getHeaders().get(X_REQUESTED_WITH).contains(XML_HTTP_REQUEST);
    }

    public static boolean isContentTypeJson(ServerHttpRequest request ) {
        return request.getHeaders().get(HttpHeaders.CONTENT_TYPE).contains(CONTENT_TYPE_JSON);
    }

    public static boolean isContentTypeJson(HttpRequest request ) {
        return request.getHeaders().get(HttpHeaders.CONTENT_TYPE).contains(CONTENT_TYPE_JSON);
    }

    public static boolean isPostRequest(ServerHttpRequest request ) {
        return HttpMethod.POST.compareTo(request.getMethod()) == 0;
    }

    public static boolean isPostRequest(HttpRequest request ) {
        return HttpMethod.POST.compareTo(request.getMethod()) == 0;
    }

    /**
     * 2、从Flux<DataBuffer>中获取字符串的方法
     * @return 请求体
     */
    public static String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        if(serverHttpRequest.getHeaders().getContentLength() == 0) {
            return org.apache.commons.lang3.StringUtils.EMPTY;
        }
        // 获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        // 获取request body
        return bodyRef.get();
    }


    /**
     * 获取请求客户端IP地址,支持代理服务器
     * @param request {@link HttpServletRequest} 对象
     * @return IP地址
     */
    public static String getRemoteAddr(ServerHttpRequest request) {

        // 1、获取客户端IP地址,支持代理服务器
        String remoteAddr = UNKNOWN; 
        for (String xheader : xheaders) {
            remoteAddr = request.getHeaders().getFirst(xheader);
            log.debug(" {} : {} " , xheader, remoteAddr);
            if (StringUtils.hasText(remoteAddr) && !UNKNOWN.equalsIgnoreCase(remoteAddr)) {  
                // 多次反向代理后会有多个ip值,第一个ip才是真实ip
                if( remoteAddr.indexOf(",") !=-1 ){
                    remoteAddr = remoteAddr.split(",")[0];
                }
                break;
            }
        }
        if (!StringUtils.hasText(remoteAddr) || UNKNOWN.equalsIgnoreCase(remoteAddr)) { 
            for (String header : headers) {

                remoteAddr = request.getHeaders().getFirst(header);
                log.debug(" {} : {} " , header, remoteAddr);

                if(StringUtils.hasText(remoteAddr) && !UNKNOWN.equalsIgnoreCase(remoteAddr)){
                    break;
                }
            }
        }

        // 2、没有取得特定标记的值
        if (!StringUtils.hasText(remoteAddr) || UNKNOWN.equalsIgnoreCase(remoteAddr)) { 
            remoteAddr = request.getRemoteAddress().getAddress().getHostAddress();
        }
        // 3、判断是否localhost访问
        if( LOCAL_HOST.equals(remoteAddr) || LOCAL_IP6.equals(remoteAddr)){
            remoteAddr = LOCAL_IP;  
        }

        return remoteAddr;
    }

    public static boolean isSameSegment(ServerHttpRequest request) {
        String localIp = request.getLocalAddress().getAddress().getHostAddress();
        String remoteIp = getRemoteAddr(request);
        log.info("localIp:{},remoteIp:{} url:{}", localIp, remoteIp, request.getPath().value());
        int mask = getIpV4Value("255.255.255.0");
        boolean flag = (mask & getIpV4Value(localIp)) == (mask & getIpV4Value(remoteIp));
        return flag;
    }

    public static int getIpV4Value(String ipOrMask) {
        byte[] addr = getIpV4Bytes(ipOrMask);
        int address1 = addr[3] & 0xFF;
        address1 |= ((addr[2] << 8) & 0xFF00);
        address1 |= ((addr[1] << 16) & 0xFF0000);
        address1 |= ((addr[0] << 24) & 0xFF000000);
        return address1;
    }

    public static byte[] getIpV4Bytes(String ipOrMask) {
        try {

            String[] addrs = ipOrMask.split("\\.");
            int length = addrs.length;
            byte[] addr = new byte[length];
            for (int index = 0; index < length; index++) {
                addr[index] = (byte) (Integer.parseInt(addrs[index]) & 0xff);
            }
            return addr;
        } catch (Exception e) {
        }
        return new byte[4];
    }

    public static String getDeviceId(ServerHttpRequest request ) {
        HttpHeaders headers = request.getHeaders();
        // 1、判断是否 Apple 设备
        String deviceId = headers.getFirst(XHeaders.X_DEVICE_IDFA);
        if(!StringUtils.hasText(deviceId)) {
            deviceId = headers.getFirst(XHeaders.X_DEVICE_OAID);
        }
        if(!StringUtils.hasText(deviceId)) {
            deviceId = headers.getFirst(XHeaders.X_DEVICE_OPENUDID);
        }
        // 2、判断是否 Android 设备
        if(!StringUtils.hasText(deviceId)) {
            deviceId = headers.getFirst(XHeaders.X_DEVICE_IMEI);
        }
        if(!StringUtils.hasText(deviceId)) {
            deviceId = headers.getFirst(XHeaders.X_DEVICE_ANDROIDID);
        }
        if(!StringUtils.hasText(deviceId)) {
            deviceId = headers.getFirst(XHeaders.X_DEVICE_OAID);
        }
        return deviceId;
    }

}
/**
 * 加密解密方式
 */
public enum CryptoType {

    /**
     * 默认的行为
     */
    NOOP,
    /**
     * 系统内部加解密
     */
    INTERNAL,
    /**
     * 弗兰科信息
     */
    FLKSEC

}

黑名单 GlobalFilter

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.biz.utils.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisOperationTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.stream.Collectors;

@Component
public class RequestBlacklistFilter implements GlobalFilter, Ordered, InitializingBean {

    private static final AntPathMatcher pathMathcer = new AntPathMatcher();
    private List<String> ignorePatterns;

    @Autowired
    private GatewayFilterProperties gatewayProperties;
    @Autowired
    private RedisOperationTemplate redisOperationTemplate;
    @Autowired
    private MessageSource messageSource;

    @Override
    public void afterPropertiesSet() throws Exception {

        List<Entry<String, String>> noneEntries = gatewayProperties.getFilterMap().entrySet().stream()
                .filter((entry) -> {
                    String[] array = StringUtils.tokenizeToStringArray(entry.getValue().toString());
                    return ArrayUtils.contains(array, "anon");
                })
                .collect(Collectors.toList());
        List<String> ignorePatterns = new ArrayList<String>();
        if (!CollectionUtils.isEmpty(noneEntries)) {
            ignorePatterns = noneEntries.stream().map(mapper -> {
                return mapper.getKey();
            }).collect(Collectors.toList());
        }
        this.setIgnorePatterns(ignorePatterns);

    }

    @Override
    public int getOrder() {
        // RequestBlacklistFilter > RequestAuthorizationFilter > RequestCryptoFilter > RequestSignOnlyFilter
        return Ordered.HIGHEST_PRECEDENCE;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 忽略部分请求
         if(!CollectionUtils.isEmpty(ignorePatterns)) {
             for (String pattern : ignorePatterns) {
                 if(pathMathcer.match(pattern, exchange.getRequest().getPath().value())) {
                     return chain.filter(exchange);
                 }
             }
         }

         ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse originalResponse = exchange.getResponse();

        Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());

        // 1、判断是否 Apple 设备
        String deviceId = WebFluxUtils.getDeviceId(serverHttpRequest);
        // 3、判断设备ID是否取到
        if (!StringUtils.hasText(deviceId)) {
            String message = getMessageSource().getMessage("app.blacklist.device.required", null, locale);
            return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(20017, message));
        }
        // 4、设备黑名单检查
        String appBlacklistKey = BlacklistRedisKey.APP_BLACKLIST.getFunction().apply("device", null);
        boolean isMemeber =  getRedisOperationTemplate().sHasKey(appBlacklistKey, deviceId);
        if (isMemeber){
            String message = getMessageSource().getMessage("app.blacklist.device.banned", null, locale);
            return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(20017, message));
        }
        return chain.filter(exchange);
    }

    public RedisOperationTemplate getRedisOperationTemplate() {
        return redisOperationTemplate;
    }

    public MessageSource getMessageSource() {
        return messageSource;
    }

    public void setIgnorePatterns(List<String> ignorePatterns) {
        this.ignorePatterns = ignorePatterns;
    }

}

请求鉴权 GlobalFilter

/**
 * Authenticate requests with header 'X-Authorization: Bearer jwt-token'.
 */
@Component
@Slf4j
public class RequestAuthorizationFilter implements GlobalFilter, Ordered, InitializingBean {

    private static final AntPathMatcher pathMathcer = new AntPathMatcher();

    /**
     * HTTP Authorization header, equal to <code>X-Authorization</code>
     */
    public static final String AUTHORIZATION_HEADER = "X-Authorization";
    public static final String AUTHORIZATION_PARAM = "token";

    private String authorizationHeaderName = AUTHORIZATION_HEADER;
    private String authorizationParamName = AUTHORIZATION_PARAM;
    private String authorizationCookieName = AUTHORIZATION_PARAM;

    private List<String> ignorePatterns;

    @Autowired
    private GatewayFilterProperties gatewayProperties;
    @Autowired
    private MessageSource messageSource;

    @Override
    public void afterPropertiesSet() throws Exception {

        this.setAuthorizationHeaderName(gatewayProperties.getAuthorizationHeaderName());
        this.setAuthorizationParamName(gatewayProperties.getAuthorizationParamName());
        this.setAuthorizationCookieName(gatewayProperties.getAuthorizationCookieName());

        List<Entry<String, String>> noneEntries = gatewayProperties.getFilterMap().entrySet().stream()
                .filter((entry) -> {
                    String[] array = StringUtils.tokenizeToStringArray(entry.getValue().toString());
                    return ArrayUtils.contains(array, "anon");
                })
                .collect(Collectors.toList());
        List<String> ignorePatterns = new ArrayList<String>();
        if (!CollectionUtils.isEmpty(noneEntries)) {
            ignorePatterns = noneEntries.stream().map(mapper -> {
                return mapper.getKey();
            }).collect(Collectors.toList());
        }
        this.setIgnorePatterns(ignorePatterns);

    }

    @Override
    public int getOrder() {
        // RequestBlacklistFilter > RequestAuthorizationFilter > RequestCryptoFilter > RequestSignOnlyFilter
        return Ordered.HIGHEST_PRECEDENCE + 100;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 忽略部分请求
        if(!CollectionUtils.isEmpty(ignorePatterns)) {
            for (String pattern : ignorePatterns) {
                if(pathMathcer.match(pattern, exchange.getRequest().getPath().value())) {
                    return chain.filter(exchange);
                }
            }
        }

        // 提取 Token
        String token = obtainToken(exchange.getRequest());;
        // 返回10022状态码和提示信息
        if (!StringUtils.hasText(token)) {

            ServerHttpResponse response = exchange.getResponse();

            Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
            String message = getMessageSource().getMessage("request.authorization.required", null, locale);

            return GatewayUtils.returnMono(response, ApiRestResponse.fail(10022, message));

        }

        return chain.filter(exchange);
    }

    protected String obtainToken(ServerHttpRequest request) {
        //    从header中获取token
        String token = request.getHeaders().getFirst(getAuthorizationHeaderName());
        if(StringUtils.hasText(token)){
            log.debug("obtain token from header : {}, token : {} ", getAuthorizationHeaderName(), token);
        } else {
            log.debug("obtain token from header : {}, token is null.", getAuthorizationHeaderName());
        }
        //    如果header中不存在token,则从参数中获取token
        if (StringUtils.isEmpty(token)) {
            token = request.getQueryParams().getFirst(getAuthorizationParamName());
            log.debug("obtain token from param : {}, token : {} ", getAuthorizationParamName(), token);
        }
        //    从 cookie 获取 token
        if (StringUtils.isEmpty(token)) {
            HttpCookie httpCookie = request.getCookies().getFirst(getAuthorizationCookieName());
            if (Objects.nonNull(httpCookie)){
                token = httpCookie.getValue();
                log.debug("obtain token from cookie : {}, token : {} ", getAuthorizationCookieName(), token);
            }
        }
        if(StringUtils.isEmpty(token)) {
            log.error("obtain token by header {}, param {}, cookie {} is null.", getAuthorizationHeaderName(), getAuthorizationParamName(), getAuthorizationCookieName());
        }
        return token;
    }

    public MessageSource getMessageSource() {
        return messageSource;
    }

    public void setIgnorePatterns(List<String> ignorePatterns) {
        this.ignorePatterns = ignorePatterns;
    }

    public String getAuthorizationHeaderName() {
        return authorizationHeaderName;
    }

    public void setAuthorizationHeaderName(String authorizationHeaderName) {
        this.authorizationHeaderName = authorizationHeaderName;
    }

    public String getAuthorizationParamName() {
        return authorizationParamName;
    }

    public void setAuthorizationParamName(String authorizationParamName) {
        this.authorizationParamName = authorizationParamName;
    }

    public String getAuthorizationCookieName() {
        return authorizationCookieName;
    }

    public void setAuthorizationCookieName(String authorizationCookieName) {
        this.authorizationCookieName = authorizationCookieName;
    }

}

请求加解密、请求签名验证 GlobalFilter

定义抽象的 GlobalFilter 实现 RequestSafetyFilter

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;

public abstract class RequestSafetyFilter implements GlobalFilter, Ordered {

    /**
     * HTTP Authorization header, equal to <code>X-Authorization</code>
     */
    public static final String AUTHORIZATION_HEADER = "X-Authorization";
    public static final String AUTHORIZATION_PARAM = "token";
    private String authorizationHeaderName = AUTHORIZATION_HEADER;
    private String authorizationParamName = AUTHORIZATION_PARAM;
    private String authorizationCookieName = AUTHORIZATION_PARAM;
    /**
     * HTTP Sign header, equal to <code>X-Sign</code>
     */
    public static final String SIGN_HEADER = "X-Sign";
    public static final String SIGN_PARAM = "sign";
    private String signHeaderName = SIGN_HEADER;
    private String signParamName = SIGN_PARAM;
    private String signCookieName = SIGN_PARAM;


    protected String obtainToken(ServerHttpRequest request) {
        //    从header中获取token
        String token = request.getHeaders().getFirst(getAuthorizationHeaderName());
        //    如果header中不存在token,则从参数中获取token
        if (!StringUtils.hasText(token)) {
            return request.getQueryParams().getFirst(getAuthorizationParamName());
        }
        //    从 cookie 获取 token
        if (!StringUtils.hasText(token)) {
            return request.getCookies().getFirst(getAuthorizationCookieName()).getValue();
        }
        return token;
    }

    protected String obtainSign(ServerHttpRequest request) {
        //    从header中获取sign
        String token = request.getHeaders().getFirst(getSignHeaderName());
        //    如果header中不存在sign,则从参数中获取sign
        if (!StringUtils.hasText(token)) {
            return request.getQueryParams().getFirst(getSignParamName());
        }
        return token;
    }

    public String getAuthorizationHeaderName() {
        return authorizationHeaderName;
    }

    public void setAuthorizationHeaderName(String authorizationHeaderName) {
        this.authorizationHeaderName = authorizationHeaderName;
    }

    public String getAuthorizationParamName() {
        return authorizationParamName;
    }

    public void setAuthorizationParamName(String authorizationParamName) {
        this.authorizationParamName = authorizationParamName;
    }

    public String getAuthorizationCookieName() {
        return authorizationCookieName;
    }

    public void setAuthorizationCookieName(String authorizationCookieName) {
        this.authorizationCookieName = authorizationCookieName;
    }

    public String getSignHeaderName() {
        return signHeaderName;
    }

    public void setSignHeaderName(String signHeaderName) {
        this.signHeaderName = signHeaderName;
    }

    public String getSignParamName() {
        return signParamName;
    }

    public void setSignParamName(String signParamName) {
        this.signParamName = signParamName;
    }

    public String getSignCookieName() {
        return signCookieName;
    }

    public void setSignCookieName(String signCookieName) {
        this.signCookieName = signCookieName;
    }

}

RequestCryptoFilter

/**
 * 传输机密性和传输完整性验过滤器,确保信息在传输过程中不被未授权方获取或泄露。
 * - 传输机密性:SM4(国密对称加密算法)
 * - 传输完整性:SM3(国密摘要签名算法)
 */
@Component
@Slf4j
public class RequestCryptoFilter extends RequestSafetyFilter implements InitializingBean {

    private static final AntPathMatcher PATH_MATHCER = new AntPathMatcher();
    @Setter
    private List<String> ignorePatterns;
    private final EnumMap<CryptoType, RequestCryptoStrategy> enumMap = new EnumMap<>(CryptoType.class);

    @Autowired
    private GatewayFilterProperties gatewayProperties;
    @Getter
    @Autowired
    private MessageSource messageSource;
    @Autowired
    private List<RequestCryptoStrategy> cryptoStrategies;

    @Override
    public int getOrder() {
        // RequestBlacklistFilter > RequestAuthorizationFilter > RequestCryptoFilter > RequestSignOnlyFilter
        return Ordered.HIGHEST_PRECEDENCE + 200;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        List<Map.Entry<String, String>> noneEntries = gatewayProperties.getFilterMap().entrySet().stream()
                .filter((entry) -> {
                    String[] array = StringUtils.tokenizeToStringArray(entry.getValue());
                    // 指定 no-crypto 与指定anon但是没指定crypto的都是要忽略掉的请求
                    return ArrayUtils.contains(array, "no-crypto") || (ArrayUtils.contains(array, "anon") && !ArrayUtils.contains(array, "crypto"));
                })
                .collect(Collectors.toList());
        List<String> ignorePatterns = new ArrayList<String>();
        if (!CollectionUtils.isEmpty(noneEntries)) {
            ignorePatterns = noneEntries.stream().map(Map.Entry::getKey).collect(Collectors.toList());
        }
        this.setIgnorePatterns(ignorePatterns);

        enumMap.putAll(cryptoStrategies.stream().collect(Collectors.toMap(RequestCryptoStrategy::getType, strategy -> strategy)));
        log.debug("CryptoStrategyProvider:{}", enumMap);

    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1、判断 Crypto 是否启用,未启用则直接跳过
        if (Objects.isNull(gatewayProperties.getCrypto()) || !gatewayProperties.getCrypto().isEnabled() || Objects.isNull(gatewayProperties.getCrypto().getType())) {
            return chain.filter(exchange);
        }

        // 2、获取 Request 和 Response 对象
        ServerHttpRequest originalRequest = exchange.getRequest();
        ServerHttpResponse originalResponse = exchange.getResponse();

        // 3、忽略部分请求
        String path = originalRequest.getPath().value();
        log.info("Path:{}", path);
        log.info("WhiteList:{}", gatewayProperties.getCrypto().getWhiteList());
        if (StringUtils.hasText(gatewayProperties.getCrypto().getWhiteList())) {
            for (String pattern : StringUtils.tokenizeToStringArray(gatewayProperties.getCrypto().getWhiteList())) {
                if (PATH_MATHCER.match(pattern, path)) {
                    return chain.filter(exchange);
                }
            }
        }
        log.info("IgnorePatterns:{}", ignorePatterns);
        if (!CollectionUtils.isEmpty(ignorePatterns)) {
            for (String pattern : ignorePatterns) {
                if (PATH_MATHCER.match(pattern, path)) {
                    return chain.filter(exchange);
                }
            }
        }

        // 4、获取 Locale
        Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
        exchange.getAttributes().put("startTime", System.currentTimeMillis());
        // 5、获取 Sign 并检查是否存在
        String sign = obtainSign(exchange.getRequest());
        // 5.1、返回1403状态码和提示信息
        if (!StringUtils.hasText(sign)) {
            String message = getMessageSource().getMessage("request.signature.required", null, locale);
            return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
        }
        // 6、获取 Token
        String token = obtainToken(exchange.getRequest());
        // 7、根据 Provider 进行解密
        CryptoType provider = Optional.ofNullable(gatewayProperties.getCrypto().getType()).orElse(CryptoType.NOOP);
        RequestCryptoStrategy cryptoStrategy = enumMap.get(provider);
        log.debug("CryptoType:{}, cryptoStrategy : {}", provider, cryptoStrategy);
        return cryptoStrategy.doExchange(exchange, chain, sign, token);
    }

}

RequestCryptoStrategy

import com.tianyin.edu.gateway.setup.crypto.CryptoType;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 请求内容的传输机密性和完整性所需的加解密策略
 */
public interface RequestCryptoStrategy {

    /**
     * 获取加解密方式
     * @return 加解密方式
     */
    CryptoType getType();

    /**
     * 对请求数据进行解密
     * @param exchange 请求上下文
     * @param chain 过滤器链
     * @param sign 签名
     * @param token 令牌
     * @return 解密后的数据
     */
    Mono<Void> doExchange(ServerWebExchange exchange, GatewayFilterChain chain, String sign, String token);

}

AbstractRequestCryptoStrategy

/**
 * 加解密抽象实现
 */
@Slf4j
public abstract class AbstractRequestCryptoStrategy implements RequestCryptoStrategy {

    private static final Set<HttpMethod> NEED_FILTER_METHOD_SET = Sets.newHashSet(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);
    private static final Set<MediaType> NEED_FILTER_MEDIA_TYPE_SET = Sets.newHashSet(MediaType.APPLICATION_JSON);

    @Getter
    private MessageSource messageSource;

    public AbstractRequestCryptoStrategy(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @Override
    public Mono<Void> doExchange(ServerWebExchange exchange, GatewayFilterChain chain, String sign, String token) {
        log.debug("Start to decrypt request data ...");

        // 获取请求体
        ServerHttpRequest originalRequest = exchange.getRequest();
        // 获取响应体
        ServerHttpResponse originalResponse = exchange.getResponse();
        // 请求头
        HttpHeaders headers = originalRequest.getHeaders();
        // 请求方法
        HttpMethod method = originalRequest.getMethod();
        // Locale
        Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
        // 满足条件,进行过滤
        if (isNeedFilterMethod(method) && isNeedFilterContentType(headers.getContentType())) {

            // 2、获取查询参数
            MultiValueMap<String, String> queryParams = originalRequest.getQueryParams();
            log.debug("Encrypted Request Query Params : {}", queryParams);
            // 2.1、加密算法类型,系统支持 SM4【必须】
            String algorithmType = queryParams.getFirst("algorithmType");
            // 2.2、采用的加密模式,系统支持 ecb,cbc,cfb,ofb,推荐 cbc,国密不允许使用 ecb 模式【必须】
            String encMode = StringUtils.defaultIfBlank(queryParams.getFirst("encMode"), "ecb").toUpperCase();
            // 2.3、加密运算所采用的填充模式,系统支持 PKCS5Padding 和 NoPadding,推荐 PKCS5Padding【必须】
            String padMode = queryParams.getFirst("padMode");
            // 2.4、Base64 格式的密钥字符串【必须】
            String key = queryParams.getFirst("key");
                   key = StringUtils.isNoneBlank(key) ? Base64.decodeStr(key) : key;
            // 2.5、Base64 格式的初始向量,加密模式为 cbc,cfb,ofb 时该参数不能为空,解码后长度 为 16 位,可自定义【非必须】
            String iv = StringUtils.defaultIfBlank(queryParams.getFirst("iv"), null);
                   iv = StringUtils.isNoneBlank(iv) ? Base64.decodeStr(iv) : iv;
            // 2.6、明文是否进行了 base64 编码 true/false 【非必须】
            boolean plainIsEncode = Optional.ofNullable(queryParams.getFirst("plainIsEncode")).map(Boolean::parseBoolean).orElse(false);
            // 2.7、进行参数校验
            if (StringUtils.isEmpty(algorithmType)) {
                String message = getMessageSource().getMessage("request.crypto.algorithmType.required", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            }
            if (StringUtils.isEmpty(encMode)) {
                String message = getMessageSource().getMessage("request.crypto.encMode.required", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            }
            if (StringUtils.isEmpty(padMode)) {
                String message = getMessageSource().getMessage("request.crypto.padMode.required", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            }
            if (StringUtils.isEmpty(key)) {
                String message = getMessageSource().getMessage("request.crypto.key.required", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            }

            // 处理没有请求体的情况
            if (originalRequest.getHeaders().getContentLength() == 0) {
                // 处理没有请求体的情况
                return handleRequestWithoutBody(exchange, chain, locale, token, sign, algorithmType, encMode, padMode, key, iv, plainIsEncode);
            }
            // 处理有请求体的情况
            return handleRequestWithBody(exchange, chain, locale, token, sign, algorithmType, encMode, padMode, key, iv, plainIsEncode);
        }
        // cryptoSwitch = 0
        // cryptoType = NOOP、INTERNAL、FLKSEC
        // 不满足条件,直接跳过
        return chain.filter(exchange);
    }

    private Mono<Void> handleRequestWithoutBody(ServerWebExchange exchange, GatewayFilterChain chain, Locale locale, String token, String sign,
                                                String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {
        try {
            // 获取请求体
            ServerHttpRequest originalRequest = exchange.getRequest();
            // 获取响应体
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 解密查询参数
            MultiValueMap<String, String> decryptQueryParams = decryptQueryParams(originalRequest.getQueryParams(), algorithmType, encMode, padMode, key, iv, plainIsEncode);

            // 5、进行签名逻辑处理
            try {

                // 5.1、提取请求头信息
                String appId = originalRequest.getHeaders().getFirst(XHeaders.X_APP_ID);
                String appChannel = originalRequest.getHeaders().getFirst(XHeaders.X_APP_CHANNEL);
                String appVersion = originalRequest.getHeaders().getFirst(XHeaders.X_APP_VERSION);

                // 5.2、构建要签名的原文
                String signContent = SignUtil.buildSignContent(appId, appVersion, appChannel, token, decryptQueryParams, null);
                // 5.2、对参数进行签名验证
                String hash = this.doSign(signContent, key, iv, plainIsEncode);
                if(!hash.equals(sign)) {
                    String message = getMessageSource().getMessage("request.signature.mismatch", null, locale);
                    return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
                }
            } catch (Exception e){
                log.error(e.getMessage());
                String message = getMessageSource().getMessage("request.signature.incorrect", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            }

            // 装饰新的请求体
            ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(originalRequest, originalResponse, decryptQueryParams, null);
            // 装饰新的响应体
            ServerHttpResponseDecorator responseDecorator = serverHttpResponseDecorator(originalResponse, algorithmType, encMode, padMode, key, iv, plainIsEncode);
            // 使用新的请求和响应转发
            ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(responseDecorator).build();
            // 放行拦截
            return chain.filter(serverWebExchange);
        } catch (Exception e) {
            log.error("密文过滤器加解密错误", e);
            String message = getMessageSource().getMessage("request.crypto.error", null, locale);
            return GatewayUtils.returnMono(exchange.getResponse(), ApiRestResponse.fail(1403, message));
        }
    }

    private Mono<Void> handleRequestWithBody(ServerWebExchange exchange, GatewayFilterChain chain, Locale locale, String token, String sign,
                                             String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode){
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            // 获取请求体
            ServerHttpRequest originalRequest = exchange.getRequest();
            // 获取响应体
            ServerHttpResponse originalResponse = exchange.getResponse();
            try {
                // 获取解密后的Query参数
                MultiValueMap<String, String> decryptQueryParams = decryptQueryParams(originalRequest.getQueryParams(), algorithmType, encMode, padMode, key, iv, plainIsEncode);
                // 获取请求Body
                String originalRequestBody = getOriginalRequestBody(dataBuffer);
                // 解密请求Body
                log.info("请求Body解密,原文:{}", originalRequestBody);
                // 1、解密请求体
                byte[] decryptBodyBytes = this.decryptBodyString(originalRequestBody, algorithmType, encMode, padMode, key, iv, plainIsEncode);

                // 2、创建新缓冲区并写入数据
                // String bodyString = new String(decryptBodyBytes, StandardCharsets.UTF_8);
                DataBuffer decryptBodyDataBuffer = originalResponse.bufferFactory().wrap(decryptBodyBytes);
                DataBufferUtils.retain(decryptBodyDataBuffer);
                String decryptRequestBody = StandardCharsets.UTF_8.decode(decryptBodyDataBuffer.asByteBuffer()).toString();
                log.debug("请求Body解密,明文:{}", decryptRequestBody);

                // 5、进行签名逻辑处理
                try {

                    // 5.1、提取请求头信息
                    String appId = originalRequest.getHeaders().getFirst(XHeaders.X_APP_ID);
                    String appChannel = originalRequest.getHeaders().getFirst(XHeaders.X_APP_CHANNEL);
                    String appVersion = originalRequest.getHeaders().getFirst(XHeaders.X_APP_VERSION);

                    // 5.2、构建要签名的原文
                    String signContent = SignUtil.buildSignContent(appId, appVersion, appChannel, token, decryptQueryParams, decryptRequestBody);
                    // 5.2、对参数进行签名验证
                    String hash = this.doSign(signContent, key, iv, plainIsEncode);
                    if(!hash.equals(sign)) {
                        String message = getMessageSource().getMessage("request.signature.mismatch", null, locale);
                        return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
                    }
                } catch (Exception e){
                    log.error(e.getMessage());
                    String message = getMessageSource().getMessage("request.signature.incorrect", null, locale);
                    return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
                }

                // 装饰新的请求体
                ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(originalRequest, originalResponse, decryptQueryParams, decryptRequestBody);
                // 装饰新的响应体
                ServerHttpResponseDecorator responseDecorator = serverHttpResponseDecorator(originalResponse, algorithmType, encMode, padMode, key, iv, plainIsEncode);
                // 使用新的请求和响应转发
                ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(responseDecorator).build();
                // 放行拦截
                return chain.filter(serverWebExchange);
            } catch (Exception e) {
                log.error("密文过滤器加解密错误", e);
                String message = getMessageSource().getMessage("request.crypto.error", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            } finally {
                DataBufferUtils.release(dataBuffer);
            }
        });
    }

    private MultiValueMap<String, String> decryptQueryParams(MultiValueMap<String, String> queryParams,
                                                             String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode)  {
        // 1、从参数中获取加密的参数【必须】
        String queryString = queryParams.getFirst(ENCRYPTED_PARAM);
        if(StringUtils.isEmpty(queryString)){
            log.debug("请求参数解密,未发现加密参数");
            return queryParams;
        }
        log.debug("请求参数解密,原文:{}", queryString);

        // 2、获取解密器
        queryString = this.decryptQueryString(queryString, algorithmType, encMode, padMode, key, iv, plainIsEncode);
        log.debug("请求参数解密,明文:{}", queryString);

        // 3、解析查询参数
        UrlQuery urlQuery = new UrlQuery();
        urlQuery.parse(queryString, StandardCharsets.UTF_8);
        for (Map.Entry<CharSequence, CharSequence> entry : urlQuery.getQueryMap().entrySet()) {
            String key1 = String.valueOf(entry.getKey());
            String value = String.valueOf(entry.getValue());
            queryParams.put(key1, Collections.singletonList(value));
        }
        // 5、移除加密参数
        queryParams.remove(ENCRYPTED_PARAM);
        log.debug("请求参数解密,解密后参数数组:{}", queryParams);
        return queryParams;
    }

    /**
     * 获取原始的请求参数
     *
     * @param dataBuffer 数据缓冲
     * @return 原始的请求参数
     */
    private String getOriginalRequestBody(DataBuffer dataBuffer) {

        // 1、创建一个容量为dataBuffer容量大小的字节数组
        byte[] bytes = new byte[dataBuffer.readableByteCount()];
        // 2、dataBuffer类容读取到bytes中
        dataBuffer.read(bytes);

        // 3、将字节数组转换为字符串
        // String bodyString = new String(bytes, StandardCharsets.UTF_8);
        CharBuffer charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes));
        return charBuffer.toString();
    }

    private boolean isNeedFilterMethod(HttpMethod method) {
        return NEED_FILTER_METHOD_SET.contains(method);
    }

    private boolean isNeedFilterContentType(MediaType mediaType) {
        return NEED_FILTER_MEDIA_TYPE_SET.contains(mediaType) || "json".equals(mediaType.getSubtype());
    }

    private ServerHttpRequestDecorator serverHttpRequestDecorator(ServerHttpRequest originalRequest, ServerHttpResponse originalResponse,
                                                                  MultiValueMap<String, String> decryptQueryParams, String decryptRequestBody) {
        return new ServerHttpRequestDecorator(originalRequest) {

            @Override
            public HttpHeaders getHeaders() {
                if(Objects.nonNull(decryptRequestBody)){
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    return httpHeaders;
                }
                return super.getHeaders();
            }

            @Override
            public MultiValueMap<String, String> getQueryParams() {
                return CollectionUtils.isEmpty(decryptQueryParams) ? super.getQueryParams() : decryptQueryParams;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                if(Objects.nonNull(decryptRequestBody)){
                    byte[] bytes = decryptRequestBody.getBytes(StandardCharsets.UTF_8);
                    return Flux.just(originalResponse.bufferFactory().wrap(bytes));
                }
                return super.getBody();
            }
        };
    }

    private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerHttpResponse originalResponse, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {
        return new ServerHttpResponseDecorator(originalResponse) {

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                // 1、判断返回体是否是Flux类型
                if (body instanceof Flux) {
                    // 2、强转body为Flux类型
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    // 3、对返回体进行加密
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {

                        // 3.2、将多个DataBuffer进行合并
                        DataBuffer dataBuffer = getDelegate().bufferFactory().join(dataBuffers);
                        // 3.3、创建一个容量为dataBuffer容量大小的字节数组
                        byte[] byteArray = new byte[dataBuffer.readableByteCount()];
                        // 3.4、dataBuffer类容读取到byteArray中
                        dataBuffer.read(byteArray);

                        // 3.5、通过DataBufferUtils.release(dataBuffer)方法释放原始的响应DataBuffer
                        DataBufferUtils.release(dataBuffer);

                        // 3.6、将byteArray转换为字符串
                        CharBuffer charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(byteArray));
                        // String originalResponseBody = new String(byteArray, StandardCharsets.UTF_8);
                        String originalResponseBody = charBuffer.toString();
                        log.debug("响应结果加密,原文:{}", originalResponseBody);

                        // 3.7、对responseBody进行加密
                        String result = encryptResponseString(originalResponseBody, algorithmType, encMode, padMode, key, iv, plainIsEncode);
                        log.debug("响应结果加密,密文:{}", result);

                        // 3.8、将加密后的数据返回
                        byte[] encryptedByteArray = result.getBytes(StandardCharsets.UTF_8);
                        getDelegate().getHeaders().setContentLength(encryptedByteArray.length);
                        return getDelegate().bufferFactory().wrap(encryptedByteArray);
                    }));
                }
                return super.writeWith(body);
            }

            @Override
            public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p -> p));
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(originalResponse.getHeaders());
                return headers;
            }
        };
    }

    protected abstract String decryptQueryString(String queryString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode);

    protected abstract byte[] decryptBodyString(String bodyString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode);

    protected abstract String encryptResponseString(String responseString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode);

    protected abstract String doSign(String signContent, String key, String iv, boolean plainIsEncode);

}

DefaultRequestCryptoStrategy

/**
 * 请求加解密内部服务实现
 * 传输机密性:SM4(国密对称加密算法)
 * 传输完整性:SM3(国密摘要签名算法)
 */
@Component
@Slf4j
public class DefaultRequestCryptoStrategy extends AbstractRequestCryptoStrategy {

    @Autowired
    private RedisOperationTemplate redisOperationTemplate;

    @Autowired
    public DefaultRequestCryptoStrategy(MessageSource messageSource) {
        super(messageSource);
    }

    @Override
    public CryptoType getType() {
        return CryptoType.INTERNAL;
    }

    @Override
    protected String decryptQueryString(String queryString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {
        // 1、判断是否需要先解码,如果需要则先Base64解码
        if(plainIsEncode){
            queryString = Base64.decodeStr(queryString);
            log.debug("Base64 Decode Request Query String : {}", queryString);
        }
        // 2、获取解密器

        SymmetricCrypto crypto = SymmetricCryptoUtil.getSymmetricCrypto(algorithmType, encMode, padMode, key, iv);
        // 3、解密请求参数
        return crypto.decryptStr(queryString);
    }

    @Override
    protected byte[] decryptBodyString(String bodyString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {
        // 1、判断是否需要先解码,如果需要则先Base64解码
        if(plainIsEncode){
            bodyString = Base64.decodeStr(bodyString);
            log.debug("Base64 Decode Request Body String : {}", bodyString);
        }
        // 2、获取解密器
        SymmetricCrypto crypto = SymmetricCryptoUtil.getSymmetricCrypto(algorithmType, encMode, padMode, key, iv);
        // 3、解密请求体
        return crypto.decrypt(bodyString);
    }

    @Override
    protected String encryptResponseString(String responseString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {
        if (StringUtils.isBlank(responseString)) {
            return responseString;
        }
        // 1、获取加密器
        SymmetricCrypto crypto = SymmetricCryptoUtil.getSymmetricCrypto(algorithmType, encMode, padMode, key, iv);
        // 2、加密响应结果
        if(plainIsEncode){
            String encryptedResponseString = crypto.encryptBase64(responseString);
            log.debug("Base64 Encode Encrypted Response String : {}", encryptedResponseString);
            return encryptedResponseString;
        }
        String encryptedResponseString = new String(crypto.encrypt(responseString, StandardCharsets.UTF_8));
        log.debug("Encrypted Response String : {}", encryptedResponseString);
        return encryptedResponseString;
    }

    @Override
    protected String doSign(String signContent, String key, String iv, boolean plainIsEncode) {
        if(plainIsEncode){
            HMac hMac = SmUtil.hmacSm3(Base64.decode(key));
            return hMac.digestBase64(signContent, StandardCharsets.UTF_8, Boolean.TRUE);
        }
        HMac hMac = SmUtil.hmacSm3(key.getBytes(StandardCharsets.UTF_8));
        return hMac.digestBase64(signContent, StandardCharsets.UTF_8, Boolean.TRUE);
    }

}

FlksecRequestCryptoStrategy

/**
 * 请求加解密服务实现
 */
@Component
@Slf4j
public class FlksecRequestCryptoStrategy extends AbstractRequestCryptoStrategy {

    @Autowired
    private static OkHttp3Template okHttp3Template;

    @Value("${spring.cloud.gateway.crypto.flksec.address:'127.0.0.1'}")
    private String address;
    @Value("${spring.cloud.gateway.crypto.flksec.port:'9443'}")
    private String port;

    @Autowired
    public FlksecRequestCryptoStrategy(MessageSource messageSource) {
        super(messageSource);
    }

    @Override
    public CryptoType getType() {
        return CryptoType.FLKSEC;
    }

    @Override
    protected String decryptQueryString(String queryString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {

        String url = String.format("https://%s:%s/api/crypto/sysDecrypt", address, port);

        Map<String, String> bodyContent = new HashMap<>();
        // 加密的算法类型,目前系统支持 sm1,sm4
        bodyContent.put("algorithmType", algorithmType);
        // 采用的加密模式,系统支持 ecb,cbc,cfb,ofb
        bodyContent.put("encMode", encMode);
        // 解密运算所采用的填充模式,系统支持 PKCS5Padding 和 NoPadding
        bodyContent.put("padMode", padMode);
        // Base64 格式的密钥字符串
        bodyContent.put("key", key);
        // Base64 格式的初始向量, 加密模式为 cbc,cfb,ofb 时该参数不能为空,解码后长度为 16 位,可自定义
        bodyContent.put("iv", iv);
        // 需要进行解密的数据
        bodyContent.put("data", queryString);
        // 明文是否编码
        bodyContent.put("plainIsEncode", String.valueOf(plainIsEncode));

        try {
            FlkSecDecryptResponseVO decryptResponse = okHttp3Template.post(url, bodyContent, FlkSecDecryptResponseVO.class);
            if (decryptResponse.getCode() == 200) {
                return StringUtils.defaultString(decryptResponse.getData());
            } else {
                throw new BizRuntimeException(decryptResponse.getMsg());
            }
        } catch (IOException e) {
            log.error("请求参数解密失败:{}", e.getMessage());
            throw new BizRuntimeException("请求参数解密失败,请稍后重试");
        }
    }

    @Override
    protected byte[] decryptBodyString(String bodyString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {

        String url = String.format("https://%s:%s/api/crypto/sysDecrypt", address, port);

        Map<String, String> bodyContent = new HashMap<>();
        // 加密的算法类型,目前系统支持 sm1,sm4
        bodyContent.put("algorithmType", algorithmType);
        // 采用的加密模式,系统支持 ecb,cbc,cfb,ofb
        bodyContent.put("encMode", encMode);
        // 解密运算所采用的填充模式,系统支持 PKCS5Padding 和 NoPadding
        bodyContent.put("padMode", padMode);
        // Base64 格式的密钥字符串
        bodyContent.put("key", key);
        // Base64 格式的初始向量, 加密模式为 cbc,cfb,ofb 时该参数不能为空,解码后长度为 16 位,可自定义
        bodyContent.put("iv", iv);
        // 需要进行解密的数据
        bodyContent.put("data", bodyString);
        // 明文是否编码
        bodyContent.put("plainIsEncode", String.valueOf(plainIsEncode));

        try {
            FlkSecDecryptResponseVO decryptResponse = okHttp3Template.post(url, bodyContent, FlkSecDecryptResponseVO.class);
            if (decryptResponse.getCode() == 200) {
                return StringUtils.defaultString(decryptResponse.getData()).getBytes(StandardCharsets.UTF_8);
            } else {
                throw new BizRuntimeException(decryptResponse.getMsg());
            }
        } catch (IOException e) {
            log.error("请求内容解密失败:{}", e.getMessage());
            throw new BizRuntimeException("请求内容解密失败,请稍后重试");
        }
    }

    @Override
    protected String encryptResponseString(String responseString, String algorithmType, String encMode, String padMode, String key, String iv, boolean plainIsEncode) {
        String url = String.format("https://%s:%s/api/crypto/sysEncrypt", address, port);

        Map<String, String> bodyContent = new HashMap<>();
        // 加密的算法类型,目前系统支持 sm1,sm4
        bodyContent.put("algorithmType", algorithmType);
        // 采用的加密模式,系统支持 ecb,cbc,cfb,ofb
        bodyContent.put("encMode", encMode);
        // 解密运算所采用的填充模式,系统支持 PKCS5Padding 和 NoPadding
        bodyContent.put("padMode", padMode);
        // Base64 格式的密钥字符串
        bodyContent.put("key", key);
        // Base64 格式的初始向量, 加密模式为 cbc,cfb,ofb 时该参数不能为空,解码后长度为 16 位,可自定义
        bodyContent.put("iv", iv);
        // 需要进行加密的数据
        bodyContent.put("data", responseString);
        // 明文是否编码
        bodyContent.put("plainIsEncode", String.valueOf(plainIsEncode));

        try {
            FlkSecEncryptResponseVO encryptResponse = okHttp3Template.post(url, bodyContent, FlkSecEncryptResponseVO.class);
            if (encryptResponse.getCode() == 200) {
                return StringUtils.defaultString(encryptResponse.getData());
            } else {
                throw new BizRuntimeException(encryptResponse.getMsg());
            }
        } catch (IOException e) {
            log.error("响应内容加密失败:{}", e.getMessage());
            throw new BizRuntimeException("响应内容加密失败,请稍后重试");
        }
    }

    @Override
    protected String doSign(String signContent, String key, String iv, boolean plainIsEncode) {
        String url = String.format("https://%s:%s/api/hmac/sm3hmac", address, port);
        Map<String, String> bodyContent = new HashMap<>();
        // 明文是否编码
        bodyContent.put("plainIsEncode", String.valueOf(plainIsEncode));
        // Base64 格式的密钥字符串
        bodyContent.put("key", key);
        // 进行杂凑的数据,数据大小建议不要超过 100M,比较大的数据可以每 100M分块计算,最后进行比较
        bodyContent.put("data", signContent);
        try {
            FlkSecSignResponseVO signResponse = okHttp3Template.post(url, bodyContent, FlkSecSignResponseVO.class);
            if (signResponse.getCode() == 200) {
                return StringUtils.defaultString(signResponse.getData());
            } else {
                throw new BizRuntimeException(signResponse.getMsg());
            }
        } catch (IOException e) {
            log.error("请求内容签名失败:{}", e.getMessage());
            throw new BizRuntimeException("请求内容签名失败,请稍后重试");
        }
    }

}

NoOpRequestCryptoStrategy

/**
 * 请求加解密内部服务实现
 */
@Component
@Slf4j
public class NoOpRequestCryptoStrategy implements RequestCryptoStrategy {

    @Override
    public CryptoType getType() {
        return CryptoType.NOOP;
    }

    @Override
    public Mono<Void> doExchange(ServerWebExchange exchange, GatewayFilterChain chain, String sign, String token) {
        log.debug("NoOpRequestCryptoStrategy .. doExchange ");
        return GatewayUtils.returnMono(exchange, chain);
    }

}

全局请求签名验证过滤器 RequestSignOnlyFilter

  • 仅进行传输完整性验证,如果需要同时进行传输机密性和传输完整性验证,请使用 RequestCryptoFilter
  • 传输完整性:SM3(国密摘要签名算法)
/**
 * 1、全局请求签名验证过滤器:
 * - 仅进行传输完整性验证,如果需要同时进行传输机密性和传输完整性验证,请使用 RequestCryptoFilter
 * - 传输完整性:SM3(国密摘要签名算法)
 * 参考资料:
 * https://www.haoyizebo.com/posts/876ed1e8/
 * https://www.cnblogs.com/grimm/p/14031241.html
 */
@Component
@Slf4j
public class RequestSignOnlyFilter extends RequestSafetyFilter implements InitializingBean {

    private static final AntPathMatcher PATH_MATHCER = new AntPathMatcher();
    public static final String ENCRYPTED_PARAM = "encryptedParam";

    private List<String> ignorePatterns;
    @Autowired
    private GatewayFilterProperties gatewayProperties;
    @Autowired
    private MessageSource messageSource;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<Entry<String, String>> noneEntries = gatewayProperties.getFilterMap().entrySet().stream()
                .filter((entry) -> {
                    String[] array = StringUtils.tokenizeToStringArray(entry.getValue());
                    // 指定no-sign与指定anon但是没指定sign的都是要忽略掉的请求
                    if(ArrayUtils.contains(array, "no-sign") || (ArrayUtils.contains(array, "anon") && !ArrayUtils.contains(array, "sign"))) {
                        return true;
                    }
                    return false;
                })
                .collect(Collectors.toList());
        List<String> ignorePatterns = new ArrayList<String>();
        if (!CollectionUtils.isEmpty(noneEntries)) {
            ignorePatterns = noneEntries.stream().map(Entry::getKey).collect(Collectors.toList());
        }
        this.setIgnorePatterns(ignorePatterns);
    }

    @Override
    public int getOrder() {
        // RequestBlacklistFilter > RequestAuthorizationFilter > RequestCryptoFilter > RequestSignOnlyFilter
        return Ordered.HIGHEST_PRECEDENCE + 300;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse originalResponse = exchange.getResponse();

        // Sign 未开启
        if(Objects.isNull(gatewayProperties.getSignature()) || !gatewayProperties.getSignature().isEnabled()) {
            return chain.filter(exchange);
        }
        // 1、忽略部分请求
        String path = exchange.getRequest().getPath().value();
        log.info("Path:{}", path);
        log.info("WhiteList:{}", gatewayProperties.getSignature().getWhiteList());
        if(StringUtils.hasText(gatewayProperties.getSignature().getWhiteList())) {
            for (String pattern : StringUtils.tokenizeToStringArray(gatewayProperties.getSignature().getWhiteList())) {
                if(PATH_MATHCER.match(pattern, path)) {
                    return chain.filter(exchange);
                }
            }
        }
        log.info("IgnorePatterns:{}", ignorePatterns);
        if(!CollectionUtils.isEmpty(ignorePatterns)) {
            for (String pattern : ignorePatterns) {
                if(PATH_MATHCER.match(pattern, path)) {
                    return chain.filter(exchange);
                }
            }
        }

        Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
        exchange.getAttributes().put("startTime", System.currentTimeMillis());

        // 提取 Token
        String token = obtainToken(serverHttpRequest);

        // 2.1、提取 Sign
         String sign = obtainSign(serverHttpRequest);
         // 2.2、返回1403状态码和提示信息
         if (!StringUtils.hasText(sign)) {
             String message = getMessageSource().getMessage("request.signature.required", null, locale);
             return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
         }
         // 3、进行签名逻辑处理
        try {

            // 3.1、提取请求头信息
            String appId = serverHttpRequest.getHeaders().getFirst(XHeaders.X_APP_ID);
            String appChannel = serverHttpRequest.getHeaders().getFirst(XHeaders.X_APP_CHANNEL);
            String appVersion = serverHttpRequest.getHeaders().getFirst(XHeaders.X_APP_VERSION);

            // 3.2、请求参数,post从请求里获取请求体
            String bodyString = this.matches(serverHttpRequest)    ? WebFluxUtils.resolveBodyFromRequest(serverHttpRequest) : null;

            // 3.3、对参数进行签名验证
            String hash = SignUtil.sm3(appId, appVersion, appChannel, token, exchange.getRequest().getQueryParams(), bodyString);
            if(!hash.equals(sign)) {
                String message = getMessageSource().getMessage("request.signature.mismatch", null, locale);
                return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
            }
        } catch (Exception e){
            log.error(e.getMessage());
            String message = getMessageSource().getMessage("request.signature.incorrect", null, locale);
            return GatewayUtils.returnMono(originalResponse, ApiRestResponse.fail(1403, message));
        }
        return GatewayUtils.returnMono(exchange, chain);
    }

    private boolean matches(ServerHttpRequest originalRequest) {
        return HttpMethod.valueOf(originalRequest.getMethodValue()).compareTo(HttpMethod.POST) == 0
                || HttpMethod.valueOf(originalRequest.getMethodValue()).compareTo(HttpMethod.PUT) == 0
                || HttpMethod.valueOf(originalRequest.getMethodValue()).compareTo(HttpMethod.PATCH) == 0;
    }

    public List<String> getIgnorePatterns() {
        return ignorePatterns;
    }

    public void setIgnorePatterns(List<String> ignorePatterns) {
        this.ignorePatterns = ignorePatterns;
    }

    public GatewayFilterProperties getGatewayProperties() {
        return gatewayProperties;
    }

    public void setGatewayProperties(GatewayFilterProperties gatewayProperties) {
        this.gatewayProperties = gatewayProperties;
    }

    public MessageSource getMessageSource() {
        return messageSource;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
}
作者:Jeebiz  创建时间:2024-11-13 15:56
最后编辑:Jeebiz  更新时间:2024-11-14 21:58