springcloud gateway api实现全信道加解密

1. 接口的使用范围

get 请求 post请求 put请求 delete请求
白名单除外

2注意事项

请求头增加一个加密版本字段,标识当前的加密算法版本:crypto-version: 1.0.0

3.加密算法

考虑到全局加密,使用AES加密方式性能更高

加密字符串:原始数据 > AES加密后的字节数组 > Base64编码处理

解密字符串:Base64密文 > AES密文 -> 原始字符串

4.报文格式

GET
url:/app/xx/xx?xx=1
加密处理
秘钥:xxxxxxxxxxxxxxxx
加密文本:{“xx”:1}
密文:xq4YR89LgUs4V5N5juKgW5hIsiOsCxBOwzX632S8NV4=
加密后的请求
/app/xx/xx?data=xq4YR89LgUs4V5N5juKgW5hIsiOsCxBOwzX632S8NV4=
POST
url:/app/xx/xx/xxx
json body:
{“xxx1”:“111”,“xxx2”:“huawei”,“xxx3”:“789”,“xxx4”:101,“xxx5”:2}
加密处理
秘钥:xxxxxxxxxxxxxxxx
加密文本:
{“xxx1”:“111”,“xxx2”:“huawei”,“xxx3”:“789”,“xxx4”:101,“xxx5”:2}
密文:1oUTYvWfyaeTJ5/wJTVBqUv0Dz0IAUQTZtxSKY9WLZZl8pILP2Sozk5yOYg9I1WTvzgbbGRDGcWV1ASpYykyS1Fq5cT8s3aLXQ6NMo0AaMOC9L0aVpR863qWso5O8aG3
加密后的请求*
json body:
{
“data”: “1oUTYvWfyaeTJ5/wJTVBqUv0Dz0IAUQTZtxSKY9WLZZl8pILP2Sozk5yOYg9I1WTvzgbbGRDGcWV1ASpYykyS1Fq5cT8s3aLXQ6NMo0AaMOjt4G9dK0WwhMGZofYuBKmdF27R8Qkr3VtZvjadtvBazJurITyE7hFcr43nlHSL5E=”
}
POST url传参 和GET格式一致

5.直接上代码

import cn.hutool.crypto.CryptoException;
import cn.iocoder.yudao.gateway.util.AesUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBufAllocator;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringEscapeUtils;
import org.reactivestreams.Publisher;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.context.annotation.Configuration;
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.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.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;


@Slf4j
@RequiredArgsConstructor
@Configuration
@ConditionalOnProperty(value = "gateway.crypto.enabled", havingValue = "true", matchIfMissing = true)
public class CryptoFilter implements GlobalFilter, Ordered {
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);

    private final CryptoProperties cryptoProperties;

    @Override  //1001
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("来了老弟");
        if (!cryptoProperties.isEnabled()) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        //校验请求路径跳过加密
//        String originalRequestUrl = RequestProvider.getOriginalRequestUrl(exchange);
        String path = exchange.getRequest().getURI().getPath();
        if (isSkip(path)) {
            return chain.filter(exchange);
        }

        HttpHeaders headers = request.getHeaders();
        MediaType contentType = headers.getContentType();

        //后期算法升级扩展,暂时只判断是否相等
        if (!cryptoProperties.getCryptoVersion().equals(headers.getFirst(cryptoProperties.getCryptoVersionHeader()))) {
            return Mono.error(new CryptoException("加密版本不支持"));
        }

        if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.DELETE ) {
            return this.handleGetReq(exchange, chain);
        } else if (request.getMethod() == HttpMethod.POST &&
                (contentType == null ||
                        MediaType.APPLICATION_JSON.equals(contentType) ||
                        MediaType.APPLICATION_JSON_UTF8.equals(contentType))) {
            return this.handlePostReq(exchange, chain);
        } else if (request.getMethod() == HttpMethod.PUT &&
                (contentType == null ||
                        MediaType.APPLICATION_JSON.equals(contentType) ||
                        MediaType.APPLICATION_JSON_UTF8.equals(contentType))) {
            return this.handlePostReq(exchange, chain);
        } else {
            return chain.filter(exchange);
        }
    }

    class CryptoServerHttpResponseDecorator extends ServerHttpResponseDecorator {
        final DataBufferFactory bufferFactory;
        boolean isPass = false;
        public CryptoServerHttpResponseDecorator(ServerHttpResponse delegate) {
            super(delegate);
            bufferFactory = delegate.bufferFactory();
        }


        @Override
        public HttpHeaders getHeaders() {
            HttpHeaders headers = super.getHeaders();
            log.info("同一个请求此处有可能调用多次,先重置为false");
            //同一个请求此处有可能调用多次,先重置为false
            isPass = false;
            if (headers.getContentType() != null &&
                    !MediaType.APPLICATION_JSON.equals(headers.getContentType()) &&
                    !MediaType.APPLICATION_JSON_UTF8.equals(headers.getContentType())) {
                //相应体ContentType只处理json
                isPass = true;
            } else if (!headers.containsKey(cryptoProperties.getCryptoVersionHeader())) {
                //添加version响应头
                headers.add(cryptoProperties.getCryptoVersionHeader(), cryptoProperties.getCryptoVersion());
            }
            return headers;
        }

        //调用 writeWith 和 writeAndFlushWith 判断: NettyWriteResponseFilter

        // application/json;charset=UTF-8 走这里
        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            if (body instanceof Flux && !isPass) {
                Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
                    DataBuffer joinDataBuffer = bufferFactory.join(dataBuffer);
                    byte[] content = new byte[joinDataBuffer.readableByteCount()];
                    joinDataBuffer.read(content);
                    DataBufferUtils.release(joinDataBuffer);
                    Map<String, String> data = new HashMap<>(1);
                    data.put(cryptoProperties.getParamName(), AesUtil.encryptToBase64(content, cryptoProperties.getAesKey()));
                    return bufferFactory.wrap(JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8));
                }));
            }
            return super.writeWith(body);
        }

        // StreamingMediaType类型:application/stream 和 application/stream+json 走这里
        @Override
        public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
            return super.writeAndFlushWith(body);
        }
    }

    @SneakyThrows
    private Mono<Void> handlePostReq(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String paramData = request.getQueryParams().getFirst(cryptoProperties.getParamName());
        MultiValueMap<String, String> queryParamMap = new LinkedMultiValueMap<>();
        final boolean queryParamsDecrypt = !StringUtils.isEmpty(paramData);
        if (queryParamsDecrypt) {
            String dataJson;
            try {
                //AES解密
                dataJson = AesUtil.decryptFormBase64ToString(paramData, cryptoProperties.getAesKey());
            } catch (Exception e) {
                log.error("请求参数解密异常: ", e);
                return cryptoError(exchange.getResponse(), "请求参数解密异常");
            }
            //构造查询参数Map
            queryParamMap = buildMultiValueMap(dataJson);
            //新的解密后的uri request
            request = this.buildNewServerHttpRequest(request, queryParamMap);
        }

        //构造一个请求包装
        final MultiValueMap<String, String> finalQueryParamMap = new LinkedMultiValueMap<>(queryParamMap);
        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {

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

            @Override
            public MultiValueMap<String, String> getQueryParams() {
                if (queryParamsDecrypt) {
                    return finalQueryParamMap;
                }
                return super.getQueryParams();
            }

            @Override
            public Flux<DataBuffer> getBody() {
                //注意: 这里需要buffer,拿到完整报文后再map解密
                return super.getBody().buffer().map(buffer -> {
                    DataBuffer joinDataBuffer = dataBufferFactory.join(buffer);
                    byte[] content = new byte[joinDataBuffer.readableByteCount()];
                    joinDataBuffer.read(content);
                    DataBufferUtils.release(joinDataBuffer);
                    String decryptData = new String(content, StandardCharsets.UTF_8);
                    log.info("post decryptData: {}", decryptData);
                    if (!queryParamsDecrypt && StringUtils.isEmpty(decryptData)) {
                        throw new CryptoException("参数格式错误");
                    } else {

                        JSONObject dataJsonObj = JSON.parseObject(decryptData);
                        if (!queryParamsDecrypt && !dataJsonObj.containsKey(cryptoProperties.getParamName())) {
                            throw new CryptoException("参数格式错误");
                        }
                        byte[] bytes = AesUtil.decryptFormBase64(dataJsonObj.getString(cryptoProperties.getParamName()), cryptoProperties.getAesKey());
                        return dataBufferFactory.wrap(Objects.requireNonNull(bytes));
                    }
                });
            }
        };
        return chain.filter(exchange.mutate()
                .request(decorator)
                .response(new CryptoServerHttpResponseDecorator(exchange.getResponse()))
                .build());
    }
    @SneakyThrows
    private Mono<Void> handleGetReq(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        if (request.getQueryParams().isEmpty()) {
            // get无参数 不走参数解密
            return chain.filter(exchange.mutate()
                    .request(request)
                    .response(new CryptoServerHttpResponseDecorator(exchange.getResponse()))
                    .build());
        }
        String paramData = request.getQueryParams().getFirst(cryptoProperties.getParamName());

        if (StringUtils.isEmpty(paramData)) {
            //有参数但是密文字段不存在
            throw new CryptoException("参数格式错误");
        }

        String dataJson;
        String s;
        try {
            //AES解密
             dataJson = AesUtil.decryptFormBase64ToString(paramData, cryptoProperties.getAesKey());
            JSONObject jsonObject = JSON.parseObject(dataJson);
            System.out.println("jsonObject = " + jsonObject);
            Iterator<Map.Entry<String, Object>> iterator = jsonObject.entrySet().iterator();
            Map.Entry<String, Object> entry;
            while (iterator.hasNext()) {
                entry = iterator.next();
                if (entry.getValue()==null){
                    iterator.remove();
                    continue;
                }
                if (entry.getValue().equals("")){
                    iterator.remove();
                }

                System.out.println("key值是:"+entry.getKey());
                System.out.println("value是:"+entry.getValue());
            }

            System.out.println("jsonObject = " + jsonObject);
            s = JSON.toJSONString(jsonObject);
            System.out.println("s = " + s);
        } catch (Exception e) {
            log.error("请求参数解密异常: ", e);
            return cryptoError(exchange.getResponse(), "请求参数解密异常");
        }
        //构造查询参数Map
        MultiValueMap<String, String> map = buildMultiValueMap(s);
        //新的解密后的uri
        ServerHttpRequest newHttpRequest = this.buildNewServerHttpRequest(request, map);
        //新的解密后的uri request
        ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(newHttpRequest) {
            @Override
            public MultiValueMap<String, String> getQueryParams() {
                return map;
            }

        };
        return chain.filter(exchange.mutate()
                .request(decorator)
                .response(new CryptoServerHttpResponseDecorator(exchange.getResponse()))
                .build());
    }

    private MultiValueMap<String, String> buildMultiValueMap(String dataJson) {
        JSONObject jsonObject = JSON.parseObject(dataJson);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>(jsonObject.size());
        for (String key : jsonObject.keySet()) {
            Object o = jsonObject.get(key);
            if(o instanceof List){
                List li= (List) o;
                map.put(key, li);
            }else {
                map.put(key, Lists.newArrayList(jsonObject.getString(key)));
            }

        }
        return map;
    }

    private ServerHttpRequest buildNewServerHttpRequest(ServerHttpRequest request, MultiValueMap<String, String> params) throws Exception {
        StringBuilder queryBuilder = new StringBuilder();
        for (String key : params.keySet()) {


            List<String> strings = params.get(key);
            if(strings.size()==1){
                queryBuilder.append(key);
                queryBuilder.append(StringPool.EQUALS);
                queryBuilder.append(params.getFirst(key));
                queryBuilder.append(StringPool.AMPERSAND);
            }else {
                for (int i = 0; i < strings.size(); i++) {
                    queryBuilder.append(key);
                    queryBuilder.append(StringPool.EQUALS);
                    queryBuilder.append(strings.get(i));
                    queryBuilder.append(StringPool.AMPERSAND);
                }
            }

        }
        queryBuilder.deleteCharAt(queryBuilder.length() - 1);

        //经过测试只覆盖 ServerHttpRequest的getQueryParams路由分发之后,无法携带过去新的参数,所以这里需要构造一个新的解密后的uri
        URI uri = request.getURI();
        URI newUri = new URI(uri.getScheme(),
                uri.getUserInfo(),
                uri.getHost(),
                uri.getPort(),
                uri.getPath(),
                queryBuilder.toString(),
                uri.getFragment());
        //构造一个新的ServerHttpRequest
        return request.mutate().uri(newUri).build();
    }

    private boolean isSkip(String path) {
        for (int i = 0; i < cryptoProperties.getSkipPathPattern().size(); i++) {
            String skipPathPattern = cryptoProperties.getSkipPathPattern().get(i);
            if (antPathMatcher.match(skipPathPattern,path)){
                return true;
            }
        }
        return false;
    }

    private Mono<Void> cryptoError(ServerHttpResponse resp, String msg) {
        resp.setStatusCode(HttpStatus.UNAUTHORIZED);
        resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        String result = JSON.toJSONString(ResponseProvider.unAuth(msg));
        DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
        return resp.writeWith(Flux.just(buffer));
    }

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

    public static void main(String[] args) {


        System.out.println(UUID.randomUUID().toString());
    }
}

还缺少相应的工具类 下期继续

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值