后端Gateway对接口参数统一加解密基于过滤器

首先准备工具类:需要加解密的接口预热

package org.springblade.gateway.provider;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName DecryptProvider
 * @Description TODO    解密接口传参
 * @Author YuanJiaLe
 * @Date 2023/10/8 15:21
 * @PackageName org.springblade.gateway.provider
 * @Version 1.0.0
 */
public class DecryptProvider {

    private static final List<String> Decrypt_URL = new ArrayList<>();

    static {
        Decrypt_URL.add("/data/post");
    }


    public static List<String> getUrlList() {
        return Decrypt_URL;
    }
}
package org.springblade.gateway.provider;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName EncryptProvider
 * @Description TODO    加密接口返参
 * @Author YuanJiaLe
 * @Date 2023/10/8 15:21
 * @PackageName org.springblade.gateway.provider
 * @Version 1.0.0
 */
public class EncryptProvider {

    private static final List<String> Encrypt_URL = new ArrayList<>();

    /**
     * 返回结果集 需要加密的接口
     */
    static {
        Encrypt_URL.add("/data/post");
    }

    public static List<String> getUrlList() {
        return Encrypt_URL;
    }

}

工具准备就绪

开始编写gateway的全局拦截器:

        1. 接口返回响应体加密过滤器:

package org.springblade.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Charsets;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springblade.common.utils.AESUtil;
import org.springblade.common.utils.RSAUtil;
import org.springblade.gateway.props.AuthProperties;
import org.springblade.gateway.provider.EncryptProvider;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Objects;

/**
 * @ClassName ResponseEncryptFilter
 * @Description TODO    对接口返回参数进行加密
 * @Author YuanJiaLe
 * @Date 2023/10/8 14:48
 * @PackageName org.springblade.gateway.filter
 * @Version 1.0.0
 */
@Data
@Slf4j
@Component
public class ResponseEncryptFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpStatus statusCode = exchange.getResponse().getStatusCode();
        //if (Objects.equals(statusCode, HttpStatus.BAD_REQUEST) || Objects.equals(statusCode, HttpStatus.TOO_MANY_REQUESTS)) {
        //    // 如果是特殊的请求,已处理响应内容,这里不再处理
        //    return chain.filter(exchange);
        //}

        // 如果是特殊的请求,已处理响应内容,这里不再处理
        if (!Objects.equals(statusCode, HttpStatus.OK)) {
            return chain.filter(exchange);
        }

        //校验路径
        if (isSkip(request.getURI().getPath())) {
            // 根据具体业务内容,修改响应体
            return modifyResponseBody(exchange, chain);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }

    /**
     * 修改响应体
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> modifyResponseBody(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator response = buildResponse(originalResponse, bufferFactory);
        return chain.filter(exchange.mutate().response(response).build());
    }

    /**
     * 构建响应
     *
     * @param originalResponse
     * @param bufferFactory
     * @return
     */
    private ServerHttpResponseDecorator buildResponse(ServerHttpResponse originalResponse, DataBufferFactory bufferFactory) {
        return new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                //只有在返回码为 200 的时候才对数据进行加密操作
                if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);
                        // 流转为字符串
                        String responseData = new String(content, Charsets.UTF_8);
                        //加密数据
                        JSONObject jsonObject = JSONObject.parseObject(responseData);
                        String code = String.valueOf(jsonObject.get("code"));
                        //返回响应码为200 才进行加密
                        if ("200".equals(code)) {
                            String data = String.valueOf(jsonObject.get("data"));
                            String key = AESUtil.getKey();
                            String encryptData = AESUtil.encrypt(data, key);
                            String encryptKey = RSAUtil.frontDecrypt(key);
                            //数据替换
                            jsonObject.put("data", new StringBuffer(encryptKey).append("&").append(encryptData).toString());
                            responseData = jsonObject.toJSONString();
                        }
                        byte[] uppedContent = responseData.getBytes(Charsets.UTF_8);
                        originalResponse.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                return super.writeWith(body);
            }

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

    /**
     * 路径校验
     *
     * @param path
     * @return
     */
    private boolean isSkip(String path) {
        return EncryptProvider.getUrlList().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)) || authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }

}


        2. 接口入参body数据解密

package org.springblade.gateway.filter;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBufAllocator;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.utils.AESUtil;
import org.springblade.common.utils.RSAUtil;
import org.springblade.gateway.props.AuthProperties;
import org.springblade.gateway.provider.DecryptProvider;
import org.springblade.gateway.provider.ResponseProvider;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @ClassName DeCodeGlobalFilter
 * @Description TODO        对接口请求数据body进行解密
 * @Author YuanJiaLe
 * @Date 2023/10/11 9:44
 * @PackageName org.springblade.gateway.provider
 * @Version 1.0.0
 */
@Data
@Slf4j
@Component
public class RequestDecryptFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpMethod method = request.getMethod();
        String path = request.getURI().getPath();
        //只针对post请求并且是指定的接口 进行解码
        if (HttpMethod.POST.equals(method) && isSkip(path)) {
            return handlePostMethod(exchange, request, chain);
        } else if (HttpMethod.PUT.equals(method) && "/studentuser/update".equals(path)) {
            return handlePostMethod(exchange, request, chain);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }

    /**
     * 路径校验
     *
     * @param path
     * @return
     */
    private boolean isSkip(String path) {
        return DecryptProvider.getUrlList().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)) || authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }

    private Mono<Void> handlePostMethod(ServerWebExchange exchange, ServerHttpRequest request, GatewayFilterChain chain) {
        MediaType contentType = request.getHeaders().getContentType();
        if (contentType == null) {
            return unAuth(exchange.getResponse(), "未识别的请求体类型");
        }
        StringBuffer paramBuffer = new StringBuffer();
        Flux<DataBuffer> body = exchange.getRequest().getBody();
        body.subscribe(buffer -> {
            //解析body 数据
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            //拷贝数据
            paramBuffer.append(charBuffer);
        });
        //获取到最后的body 中的数据
        String param = paramBuffer.toString();
        JSONObject JsonData = null;
        if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
            JsonData = JSONObject.parseObject(param);
        }
        if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
            JsonData = handleFormURLEncoded(param);
        }
        if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
            JsonData = JSON.parseObject(handleFormData(param, contentType.toString()));
        }
        if (JsonData == null) {
            return unAuth(exchange.getResponse(), "不支持的请求体类型");
        }
        String res;
        String[] split;
        //解密请求体
        split = String.valueOf(JsonData.get("data")).split("&");
        res = AESUtil.decrypt(split[1], RSAUtil.decrypt(split[0]));

        //如果是需要替换k-v类型得解密后的  循环替换解密后的数据【暂时用不到】
        //for (String str : JsonData.keySet()) {
        //    //解密前的值
        //    String enCode = JsonData.getString(str);
        //    try {
        //        //解密请求体
        //        split = enCode.split("&");
        //        res = AESUtil.decrypt(split[1], RSAUtil.decrypt(split[0]));
        //    } catch (Exception e) {
        //        return unAuth(exchange.getResponse(), "解密失败");
        //    }
        //    //替换
        //    param = param.replace(enCode, res);
        //}

        //数据替换
        String bodyStr = res;
        DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                int length = bodyStr.getBytes().length;
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_TYPE);
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.setContentLength(length);
                httpHeaders.set(HttpHeaders.CONTENT_TYPE, contentType.toString());
                // 设置CONTENT_TYPE
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return bodyFlux;
            }
        };
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    private JSONObject handleFormURLEncoded(String param) {
        JSONObject jsonObject = new JSONObject();
        String[] split = param.split("&");
        for (String s : split) {
            String[] split1 = s.split("=");
            jsonObject.put(split1[0], split1[1]);
        }
        return jsonObject;
    }


    protected DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }

    private String handleFormData(String str, String contentType) {
        String sep = "--" + contentType.replace("multipart/form-data;boundary=", "");
        String[] strs = str.split("\r\n");
        boolean bankRow = false;// 空行
        boolean keyRow = true;// name=xxx行
        boolean append = false;// 内容是否拼接换行符
        Map<String, String> params = new LinkedHashMap<>();// 这里保证接收顺序,所以用linkedhashmap
        String s = null, key = null;
        StringBuffer sb = new StringBuffer();
        for (int i = 1, len = strs.length - 1; i < len; i++) {
            s = strs[i];
            if (keyRow) {
                key = s.replace("Content-Disposition: form-data; name=", "");
                key = key.substring(1, key.length() - 1);
                keyRow = false;
                bankRow = true;
                sb = new StringBuffer();
                append = false;
                continue;
            }
            if (sep.equals(s)) {
                keyRow = true;
                if (null != key) {
                    params.put(key, sb.toString());
                }
                append = false;
                continue;
            }
            if (bankRow) {
                bankRow = false;
                append = false;
                continue;
            }
            if (append) {
                sb.append("\r\n");
            }
            sb.append(s);
            append = true;
        }
        if (null != key) {
            params.put(key, sb.toString());
        }
        return JSON.toJSONString(params);// 这里是alibaba的fastjson,需要引入依赖
    }

    private Mono<Void> unAuth(ServerHttpResponse resp, String msg) {
        ObjectMapper objectMapper = new ObjectMapper();
        //设置状态码
        resp.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        //指定编码,否则在浏览器中会中文乱码
        resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        String result = "";
        try {
            result = objectMapper.writeValueAsString(ResponseProvider.unAuth(msg));
        } catch (JsonProcessingException e) {
            log.error(e.getMessage(), e);
        }
        DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
        return resp.writeWith(Flux.just(buffer));
    }
}

         3.因为body只能读取一次,所以需要再写个

        全局过滤器 解决body流重复获取    
    
package org.springblade.gateway.filter;

import lombok.Data;
import org.springblade.gateway.props.AuthProperties;
import org.springblade.gateway.provider.DecryptProvider;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @ClassName CacheRequestBodyFilter
 * @Description TODO    全局过滤器 解决body流重复获取
 * @Author YuanJiaLe
 * @Date 2023/10/11 9:43
 * @PackageName org.springblade.gateway.provider
 * @Version 1.0.0
 */
@Data
@Component
public class CacheRequestBodyFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        //如果不是指定接口 直接放行 不用处理
        if (!isSkip(request.getURI().getPath())) {
            return chain.filter(exchange);
        }

        if (request.getHeaders().getContentType() == null) {
            return chain.filter(exchange);
        }
        HttpMethod method = request.getMethod();
        if (method == null || method.matches("GET") || method.matches("DELETE")) {
            return chain.filter(exchange);
        }
        //当body中没有缓存时,只会执行这一个拦截器, 原因是fileMap中的代码没有执行,所以需要在为空时构建一个空的缓存
        DefaultDataBufferFactory defaultDataBufferFactory = new DefaultDataBufferFactory();
        DefaultDataBuffer defaultDataBuffer = defaultDataBufferFactory.allocateBuffer(0);
        //构建新数据流, 当body为空时,构建空流
        Flux<DataBuffer> bodyDataBuffer = exchange.getRequest().getBody().defaultIfEmpty(defaultDataBuffer);
        return DataBufferUtils.join(bodyDataBuffer)
                .flatMap(dataBuffer -> {
                    DataBufferUtils.retain(dataBuffer);
                    Flux<DataBuffer> cachedFlux = Flux
                            .defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return cachedFlux;
                        }
                    };
                    //exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, cachedFlux);
                    return chain.filter(exchange.mutate().request(mutatedRequest).build());
                });
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 路径校验
     *
     * @param path
     * @return
     */
    private boolean isSkip(String path) {
        return DecryptProvider.getUrlList().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)) || authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }
}
以上三个过滤器加上俩个工具类数据预热 就完成了 动态对接口参数加解密操作,注意重写
getOrder() 方法的返回值 一定要和我一样

完美运行

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值